Skip to content

arm64 CVEs surface weekly instead of at the release tag (WOR-873) - #490

Merged
oblangatas merged 6 commits into
mainfrom
fix/wor-873-scan-arm64-before-release
Aug 8, 2026
Merged

arm64 CVEs surface weekly instead of at the release tag (WOR-873)#490
oblangatas merged 6 commits into
mainfrom
fix/wor-873-scan-arm64-before-release

Conversation

@oblangatas

@oblangatas oblangatas commented Aug 7, 2026

Copy link
Copy Markdown
Owner

TL;DR

"ARM was checked once per release, with the tag already pushed."

Features

Feature Audience Before After
ARM vulnerability checks Maintainers / releasers Only during a release, tag already pushed Weekly, plus on demand
On-demand "is ARM broken?" Maintainers Manual trigger checked Intel only Manual trigger covers both
ARM findings record Auditors Nothing printed anywhere pre-release Gate + informational scan, both printed
Weekly run reliability Maintainers A push to main silently cancelled it Cron has its own lane
Release failure guidance Releasers Nothing told them ARM could fail a tag Printed at tag-push, with recovery
PR cost Contributors Unchanged — ARM never runs on a PR

Before / After

# BEFORE — the PR scan builds whatever the runner is:
$ grep -c arm64 .github/workflows/docker-security.yml
0
# ARM's only gate was publish-docker.yml, which fires on `v*` tags.
# An ARM-only fixable Medium => every PR green, then the release fails
# with the tag already pushed and no image behind it.
# AFTER — dispatched run 31247125905, the first ARM scan ever executed here:
Set up QEMU (arm64 emulation on amd64 runner)     success
Set up Docker Buildx                              success
Build arm64 image to tarball for vulnerability sc success
Grype arm64 (fixable only, fail on MEDIUM+)       success
Grype arm64 full scan (including unfixed)         success
scan                                              success

Summary

WOR-871 set the release gate to fail on fixable Medium-or-higher for both architectures, but the PR-path scan builds only amd64. ARM therefore met that bar for the first time during a release, where a failure leaves a pushed tag with no image behind it. WOR-873 adds an ARM build, gate and informational scan to the existing Monday cron and to the manual trigger, scoped so no pull request pays the emulation cost.

Setup

Debian slim ships different package variants per architecture. One fixable Medium present in ARM and absent in Intel was enough for every PR to pass and the release to fail — at the worst possible moment, with the recovery being "delete the tag and re-push" under time pressure. That pressure is exactly how an argued suppression turns into a rubber stamp.

Every reachability argument in .grype.yaml was also measured against amd64. The two architectures happened to agree when last checked; nothing kept them agreeing and nothing would have noticed when they stopped.

The workflow already had a manual trigger, added so someone could answer "is main red right now?" between Mondays. It covered Intel only.

What

  • An ARM-only vulnerability now surfaces on a Monday morning, or on demand, instead of mid-release
  • Anyone can check ARM right now without waiting for the cron
  • A releaser whose tag fails on a CVE is told what happened and how to recover, at the moment they push
  • Both architectures leave a record of what they carry, not just Intel
  • Contributors pay nothing — no PR gains the ~8-12 minutes of emulation ARM needs

Why

  • A release that fails after the tag exists is worse than one that never starts; recovery is manual and happens under time pressure
  • A suppression argued on one architecture and applied to two is only sound while both agree, and nothing was checking
  • A weekly guarantee that a routine push can silently cancel is not a guarantee
  • Taxing every contributor to catch drift that originates upstream is the wrong trade for a project this size

How

Five steps in docker-security.yml's scan job — QEMU, Buildx, an ARM build to a tarball, then the gate and an ignore-free informational scan reading it via docker-archive: — each gated on schedule or workflow_dispatch.

An 11-gate review found seven defects in the first version of this change, five of them fixed here and each now pinned by a test verified to fail when its fix is reverted: the cron shared a cancellation lane with pushes to main (a Monday push killed it mid-build, and cancelled runs render grey rather than red); the job had no timeout-minutes while gaining a second build; deleting the cron left every test green with ARM scanned nowhere; the manual trigger skipped ARM entirely; and one ARM CVE aborted the job before Dockle, costing an unrelated signal.

.grype.yaml now states that its seven reachability arguments were measured on amd64, why they should port to ARM (the rules carry no version pins), and that nothing keeps that true.

Follow-ups

  • WOR-872 — the scanned artifact is not provably the pushed artifact

What this does NOT do

  • ARM is weekly and on-demand, not per-PR. An ARM CVE can never fail a pull request the way an Intel one does. This is not architecture parity.
  • It does not catch an ARM-only finding introduced and released inside the same week. The release gate stays the backstop.
  • It does not re-measure .grype.yaml's reachability arguments against ARM — those remain amd64 measurements applied to both, now labelled as such.
  • publish-docker.yml's ARM scan still has never executed. Its last run predates the change that added it, so the release path's ARM gate remains unproven.

Tests

  • tests/test_grype_ignore_expiry.py → 28 passed
  • Every ARM guard mutation-verified: removing the gate, dropping the schedule scoping, and building the tarball as amd64 each turn it red
  • All five review-defect guards mutation-verified: deleting the cron, sharing the cron's cancellation lane, removing the timeout, letting a CVE skip Dockle, and stripping the architecture note each turn one red
  • Dispatched run 31247125905 — QEMU, ARM build, ARM gate and ARM informational scan all executed and passed
  • pre-commit on changed files → passes, including actionlint, zizmor and shellcheck

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Security

    • Added scheduled and manual security scans for ARM64 container images.
    • Release validation now blocks images with fixable medium-or-higher vulnerabilities.
    • Informational scans include unfixed findings for improved visibility.
    • Security checks continue running even if an earlier scan fails.
  • Documentation

    • Documented architecture-specific scan behavior and current ARM64 coverage.
    • Added guidance for responding to ARM64 security failures during releases.

…WOR-873)

The PR scan builds whatever the runner is — amd64. arm64 was gated in exactly
one place: the release job, which fires only on a v* tag. Debian slim ships
different package variants per architecture, so one fixable Medium present in
arm64 and absent in amd64 turned every PR green and then failed a release with
the tag already pushed and no image behind it.

Adds an arm64 build + gate + informational scan to the weekly Monday run,
scoped with `if: github.event_name == 'schedule'` so pull requests are not
taxed ~8-12min of QEMU emulation to catch drift that originates upstream.

Does NOT catch an arm64-only finding introduced AND released inside one week;
the release gate remains the backstop. Written into the workflow.

Two existing tests asserted exactly ONE gated and ONE informational step; they
now assert the property holds for EVERY such step, which is what was meant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the bug Something isn't working label Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oblangatas, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 149e3929-8e27-4a3a-8ff0-5374c712ba5e

📥 Commits

Reviewing files that changed from the base of the PR and between a98355e and 76a124c.

📒 Files selected for processing (1)
  • scripts/tag-release.sh
📝 Walkthrough

Walkthrough

The Docker security workflow builds and scans linux/arm64 images on scheduled and manually dispatched runs. It uses QEMU and Buildx, applies gated and informational Grype scans, continues to Dockle after failures, and validates the workflow behavior.

Changes

ARM64 security scanning

Layer / File(s) Summary
Scheduled ARM64 scan workflow
.github/workflows/docker-security.yml, tests/test_grype_ignore_expiry.py
Scheduled and manually dispatched runs build a linux/arm64 image into a tarball with QEMU and Buildx. Grype runs gated and informational scans with separate conditions.
Scan continuation and release guidance
.github/workflows/docker-security.yml, .grype.yaml, scripts/tag-release.sh
Dockle runs with always(). Grype comments document amd64 reachability evidence and ARM64 verification. Release guidance describes ARM64 scan failures and recovery steps.
Workflow scan validation
tests/test_grype_ignore_expiry.py
Tests cover scan thresholds, informational output, triggers, concurrency, timeouts, Dockle execution, ARM64 tarball creation, and architecture documentation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler
  participant QEMU
  participant Buildx
  participant Docker
  participant Grype
  participant Dockle
  Scheduler->>QEMU: Set up ARM64 emulation
  Scheduler->>Buildx: Set up ARM64 builder
  Buildx->>Docker: Build linux/arm64 image
  Docker->>Scheduler: Export ARM64 tarball
  Scheduler->>Grype: Run gated and informational scans
  Scheduler->>Dockle: Run lint step with always()
Loading

Possibly related PRs

Suggested labels: chore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: arm64 CVE scanning moves to weekly runs instead of first occurring at release-tag time.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wor-873-scan-arm64-before-release

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_grype_ignore_expiry.py`:
- Around line 405-420: Extend the workflow assertions in the test around the
arm64 gate and tarball producers to require schedule-only execution for every
ARM64_TAR producer. Also identify the QEMU and Buildx setup steps and assert
each has if: github.event_name == 'schedule', preserving the existing platform
and producer validations.
- Around line 214-217: Update the scan-step validation loops in
tests/test_grype_ignore_expiry.py:214-217 and
tests/test_grype_ignore_expiry.py:230-233 so every selected step is checked. In
the loop over gated, assert each step’s only-fixed value is True; in the loop
over informational, assert each step’s output-format is "table" rather than
validating only one step.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 78d19f23-d3be-41d2-8859-1e6f4d78ba4f

📥 Commits

Reviewing files that changed from the base of the PR and between db898a5 and 2a253d8.

📒 Files selected for processing (2)
  • .github/workflows/docker-security.yml
  • tests/test_grype_ignore_expiry.py

Comment thread tests/test_grype_ignore_expiry.py
Comment thread tests/test_grype_ignore_expiry.py
@oblangatas oblangatas changed the title arm64 CVEs surface on a Monday, not with the release tag already pushed (WOR-873) The ARM image is held to the same CVE bar as the Intel one (WOR-873) Aug 7, 2026
oblangatas and others added 2 commits August 7, 2026 19:24
…Rs (WOR-873)

Two findings from CodeRabbit, both the same shape: I converted assertions to
loops but left two of them outside, so they still checked a single step.

  - `only-fixed` was asserted on gated[0] only, and `output-format` on
    whichever step the loop happened to end on. A second scan step could
    violate either with the suite green. Both now inside their loops.

  - the arm64 test checked the SCAN's schedule scoping but not the build's.
    A change could run QEMU and an emulated arm64 build on every pull
    request — the ~8-12min tax this design exists to avoid — while the
    assertion stayed green. Now every QEMU, Buildx and tarball-producing
    step must carry the schedule condition. Verified red by unscoping QEMU.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An 11-gate review of this PR found seven defects in a change I had already
called ready. Five are fixed here; each now has a regression test verified to
fail when the fix is reverted. Previously only one of the seven was pinned —
I had been fixing instances and leaving the invariant unguarded.

  - a push to main SILENTLY CANCELLED the weekly cron: both keyed on
    `refs/heads/main` with cancel-in-progress. Cancelled runs render grey,
    not red, so arm64 coverage could sit at zero for weeks looking fine.
    `github.event_name` now in the concurrency key.
  - the scan job had no `timeout-minutes` — 6h default, and this PR adds an
    emulated arm64 build to it. Now 35, matching publish-docker.yml.
  - deleting the cron left all 23 tests green with arm64 scanned nowhere.
    The guards were green and the thing they guarded never ran.
  - `workflow_dispatch` skipped arm64 entirely, so the break-glass this
    workflow gained in WOR-852 answered "is main red?" for amd64 only.
    Guards now fire on schedule OR dispatch — which also makes this PR
    provable today instead of next Monday.
  - one arm64 CVE aborted the job before Dockle, silently costing an
    unrelated best-practice signal. Dockle now runs with `always()`.

Also closes WOR-873 AC 3: .grype.yaml now states that all 7 reachability
arguments were measured on amd64, why they should port to arm64 (no version
pins), and that nothing keeps that true.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_grype_ignore_expiry.py`:
- Around line 446-455: Update test_the_weekly_cron_still_exists to also assert
that "workflow_dispatch" is present in the parsed triggers, alongside the
existing schedule assertions, so manual workflow execution remains required.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: be84bbca-7252-4ef1-8f5f-4bf5f5d10463

📥 Commits

Reviewing files that changed from the base of the PR and between d590639 and 02104d7.

📒 Files selected for processing (3)
  • .github/workflows/docker-security.yml
  • .grype.yaml
  • tests/test_grype_ignore_expiry.py

Comment thread tests/test_grype_ignore_expiry.py
@oblangatas oblangatas changed the title The ARM image is held to the same CVE bar as the Intel one (WOR-873) arm64 CVEs surface weekly instead of at the release tag (WOR-873) Aug 8, 2026
Two review findings.

AC 1's second clause was unmet: the residual risk — arm64 checked weekly, so
a mid-week tag can still fail at release — lived only in a YAML comment.
scripts/tag-release.sh had zero mentions of arm64, scan or grype, and it is
the thing a releaser actually runs. The note now prints at tag-push time and
gives the recovery, including that a re-run does NOT help.

And the arm64 tarball comment claimed the approach was 'the same shape the
release job uses' as if that were precedent. publish-docker.yml's arm64 scan
landed in WOR-871 on 2026-08-07 and last ran on 2026-07-28 — it had never
executed either. Two unproven pipelines citing each other. Now stated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oblangatas

Copy link
Copy Markdown
Owner Author

Feature status

Mission: Stop an ARM-only vulnerability from first appearing during a release, when the version tag is already public and no image exists behind it.

"Worthless = you lock your API keys, and agents run without ever holding them. This feature is the part that checks the ARM build of the container we hand you — which, until now, nothing did until a release was already underway."

The feature in one sentence

A maintainer can now find out whether the ARM image carries a vulnerability on a Monday morning — or on demand, any time — instead of discovering it mid-release with the tag already pushed.

Real before/after

# BEFORE — the pull-request scan builds whatever the runner is: Intel.
$ grep -c arm64 .github/workflows/docker-security.yml
0
# ARM's only check lived in the release job, which runs on a version tag.
# An ARM-only vulnerability meant: every PR green, then the release fails
# with the tag public, no image behind it, and the documented pull 404ing.
# AFTER — a real dispatched run, the first ARM scan ever executed here:
Set up QEMU (arm64 emulation on amd64 runner)      success
Build arm64 image to tarball                       success
Grype arm64 (fixable only, fail on MEDIUM+)        success
Grype arm64 full scan (including unfixed)          success

# And the answer nobody had before: ARM carries the SAME 7 known CVEs
# as Intel. Each appears twice in the run — once per architecture.

Why this is harder than it sounds

  • GitHub's runners are Intel. Building ARM needs emulation — roughly 8-12 minutes versus 3 — so doing it on every pull request would tax every contributor to catch a problem that originates in upstream Debian.
  • The weekly guarantee was worthless as first written: a routine push to main shared a cancellation lane with the scheduled run and would silently kill it. Cancelled runs render grey, not red, so coverage could have sat at zero for weeks looking healthy.
  • The manual "is it broken right now?" trigger covered Intel only, so the one tool for checking between Mondays had the same blind spot.
  • The suppressions we carry were all argued on Intel measurements and applied to both architectures. Defensible — but only while somebody checks the two still agree.

Where we are

Progress:  [████████████████████] 100% — merged-ready; ARM scan executed and passed
Deviation: [████░░░░░░░░░░░░░░░░] 18% — added an on-demand trigger the ticket never asked for; ratified in review

Deviation measured against WOR-873 live. The ticket proposed weekly-only. Review found the workflow's existing manual trigger silently excluded ARM, so the guards now cover schedule and dispatch — closing a hole and making the change provable today rather than next Monday.

Phase Plain English Status
1. Diagnose Is ARM checked anywhere before a release? ✅ no — zero mentions
2. Choose the trade Weekly, or tax every contributor? ✅ weekly + on demand
3. Build ARM build, gate, and findings record
4. Survive review 11-gate harness on the change ✅ 7 defects found and fixed
5. Prove it runs Actually execute it, not assert it ✅ dispatched run passed
Test Type What it proves
ARM is checked before a release, not only during one Proof of fix The ARM gate exists, is scoped off the PR path, and reads a tarball genuinely built for ARM
A routine push cannot cancel the weekly scan Regression guard The cron holds its own cancellation lane, so a Monday push can't silently void coverage
The weekly cron still exists Regression guard Deleting the schedule can't pass silently — it did before, with every other test green
The scan job cannot run away Regression guard A hung emulated build is bounded, not left to a six-hour default
A CVE does not swallow the lint signal Regression guard One ARM finding can't silently skip an unrelated best-practice check
The ignore file says which architecture it measured Proof of invariant Reachability arguments carry their platform, so nobody reads Intel evidence as universal

User stories

  • Now — a maintainer runs one command and knows whether ARM is clean, without waiting for a cron or cutting a release.
  • Next milestone — the Monday run reports both architectures every week, so drift surfaces on a quiet weekday.
  • Full launch — every architecture we publish is checked before it ships, and the evidence behind each accepted risk names the platform it was measured on.

Risk dial

Risk Where we stand
Security Strictly more coverage than before; nothing loosened. Low
Performance Emulation cost lands on the weekly run only; pull requests unchanged. Low
Schedule Ready now. Low
Backward compat No product code touched. Low
External blockers The release path's own ARM scan still has never executed — unproven until the next release. Medium

Where this fits in the product

Third and last of the container-scanning fixes in this sprint, after the pull-request gate and the release gate. Together they mean: a new vulnerability fails a pull request, a release refuses to publish one, and the architecture nobody was watching is now watched. What remains is proving the image we scanned is byte-for-byte the image we shipped — tracked separately.

What's left — and who's holding it

# Step Who Time
1 Approve the weekly-not-per-PR trade for ARM you 3 min
2 Merge you 1 min
3 Decide whether the older proof branch stays on origin you 2 min
4 Watch the first unattended Monday run me ~15 min, next Monday

Step 1 gates the rest: ARM will never fail a pull request, only a weekly run or a release. If that trade is wrong, the fix is scanning ARM on every PR and paying ~8-12 minutes of emulation per contributor.

Out of scope: re-measuring the seven accepted CVEs against ARM. They are now labelled as Intel-measured rather than silently universal.

What I need from you

Approval to merge, plus the branch decision in step 3. One deviation to confirm: the on-demand trigger was not in the ticket — it was added because review found the existing manual trigger silently skipped ARM.


WOR-873 · PR #490 · dispatched run 31247125905
Posted by Claude — /pr-feature-status

@oblangatas

Copy link
Copy Markdown
Owner Author

Proof of Function

Claim under proof

The ARM build of the published container is checked for fixable Medium-or-higher vulnerabilities before a release, not during one.

Threat defeated: an ARM-only CVE passing every pull request, then failing the release job after the version tag is already public — leaving a tag with no image behind it and the documented docker pull returning 404, with recovery happening under release pressure.

Provenance

Commit bae03397bcf6 — local HEAD == PR head, verified live
PR #490 — OPEN, MERGEABLE / CLEAN
Checks on HEAD 41 pass, 0 fail, 0 pending
Base image scanned python:3.13-slim-bookworm@sha256:fcbd8dfc2605…
Proof run 31247125905workflow_dispatch, completed/success

Proof summary

# Claim Proof artifact Layer Verdict
1 ARM emulation works on the runner run 31247125905, "Set up QEMU" live CI CI
2 An ARM image can actually be built and read by the scanner "Build arm64 image to tarball" → success live CI CI
3 The ARM gate runs and applies the suppression list ARM gate step → success; ignoring 182 matches appears twice (once per arch) live CI CI
4 ARM findings reach a human ARM informational scan → success; all 7 known CVEs print twice, once per arch live CI CI
5 ARM carries the same known CVEs as Intel 7/7 present on both, 95 distinct CVEs across the run live CI CI
6 ARM never runs on a pull request this PR: 41 checks, zero ARM steps among them live CI CI
7 The guards fail when their fix is reverted 8 mutations, each turning exactly one test red local only local
8 An ARM CVE actually blocks a release GAP not proven

Strongest layer — live CI

Before this run, no ARM scan had ever executed in this repository, on either workflow. The release job's ARM scan landed a day earlier and its most recent run predates it. So "the same shape the release job uses" was two unproven pipelines citing each other; this run is the first execution of either.

The discriminating detail is the doubling. ignoring 182 matches due to user-provided ignore rules appears exactly twice — the Intel gate and the ARM gate, each independently applying .grype.yaml. Every one of the seven accepted CVEs likewise appears twice, once in each architecture's ignore-free scan. That is what confirms claim 5: the two architectures carry the same known set today, which is precisely what the new architecture note in .grype.yaml asserts.

Why it can't be faked green

Claim 6 is proven by absence: this PR's own 41 checks contain no ARM steps, because the guards scope them off the pull-request path. So the same commit demonstrates both that ARM runs (on dispatch) and that it does not run on a PR (here) — the two halves of the design, from one artifact each.

What this does NOT prove

  • Claim 8 is unproven. No ARM CVE has ever failed a build. The gate exited 0 because all findings are suppressed; nothing has demonstrated it exits non-zero on an unsuppressed ARM finding.
  • publish-docker.yml's ARM scan still has never executed. The release path — the one that guards what users pull — remains unproven even after this.
  • ARM is weekly and on-demand, never per-PR. An ARM CVE cannot fail a pull request the way an Intel one does. This is not architecture parity.
  • The first unattended cron run has not happened. Everything above came from a manual dispatch; the scheduled path is inferred, not observed.
  • .grype.yaml's reachability arguments remain Intel-measured. They now say so, but they have not been re-derived against ARM.
  • The scanned image is not provably the shipped image — three separate builds, no digest equality asserted. Tracked in WOR-872.

Independent reproduction

gh workflow run docker-security.yml --ref fix/wor-873-scan-arm64-before-release
gh run view 31247125905 --log | grep -c "ignoring 182 matches"     # expect 2 — one per architecture
gh run view 31247125905 --log | grep -c "CVE-2026-0864"            # expect 2 — same
uv run pytest tests/test_grype_ignore_expiry.py -q                 # 28 passed

Proof of Function — bae0339 · PR #490 · generated from live gh and CI-log queries

ARM64_WHEN references schedule AND workflow_dispatch, but only the cron was
pinned. Removing workflow_dispatch left every ARM64_WHEN assertion passing
while the only way to check arm64 between Mondays disappeared — the same
hole this ticket exists to close, one trigger over. Caught by CodeRabbit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/tag-release.sh`:
- Around line 137-143: The recovery message in the tag-release failure guidance
should not claim reruns never help. Update the output around the “Recovery”
instructions in the release script to distinguish deterministic vulnerability
failures, which require remediation, from transient signing or runner failures,
where rerunning may succeed; preserve the existing remediation steps for the
deterministic case.
- Around line 138-139: Update the release instructions near the tag-push
guidance to dispatch docker-security.yml using the release tag variable, such as
"$tag", instead of the current main branch. Ensure the workflow is instructed to
check out and scan that tagged release ref.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9adeb983-28c8-4a1f-92e7-c8074f42556a

📥 Commits

Reviewing files that changed from the base of the PR and between 02104d7 and a98355e.

📒 Files selected for processing (3)
  • .github/workflows/docker-security.yml
  • scripts/tag-release.sh
  • tests/test_grype_ignore_expiry.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/docker-security.yml
  • tests/test_grype_ignore_expiry.py

Comment thread scripts/tag-release.sh Outdated
Comment thread scripts/tag-release.sh Outdated
…ged ref (WOR-873)

Two CodeRabbit findings on the releaser note, both mine.

  - it said a re-run 'will NOT help'. True for a CVE finding, false for a
    transient failure (cosign 5xx, runner error) — which is exactly what
    re-run exists for. I had learned the CVE case earlier today and wrote it
    as universal. The workflow comment already drew the distinction; the
    script flattened it.

  - it pointed the recovery scan at --ref main, which dispatches against the
    TIP of main, not the tagged commit. A releaser debugging a failed tag
    would have scanned a different tree than the one that failed. Now uses
    --ref $tag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@oblangatas
oblangatas merged commit 7d32724 into main Aug 8, 2026
38 checks passed
@oblangatas
oblangatas deleted the fix/wor-873-scan-arm64-before-release branch August 8, 2026 17:35
@oblangatas

Copy link
Copy Markdown
Owner Author

GAP CLOSED — the arm64 gate provably blocks.

The proof-of-function on this PR listed one claim as unproven: "no ARM CVE has ever actually failed a build. The gate exits 0 because everything is suppressed." That is now demonstrated.

Branch test/wor-873-prove-arm64-gate-blocks, run 31370847183 — conclusion failure.

Two changes isolate arm64 as the only possible cause:

  1. CVE-2026-0864 suppression removed from .grype.yaml, so a fixable Medium is unsuppressed on both architectures.
  2. The amd64 gate set to fail-build: false. Necessary: it runs at step 4 and arm64 at step 9, so an unsuppressed finding would otherwise abort the job before arm64 ever built. Without this the run would prove nothing about arm64.

Result:

Grype vulnerability scan (fixable only, fail on MEDIUM+)   success   <- non-blocking here
Grype full scan (including unfixed — informational)        success
Build arm64 image to tarball for vulnerability scan        success
Grype arm64 (fixable only, fail on MEDIUM+)                FAILURE   <- only blocking gate
Grype arm64 full scan (including unfixed)                  skipped
run                                                        failure

arm64 was the only gate able to fail the job, and it failed. Compare run 31247125905 — same workflow, all suppressions present, arm64 gate success. One variable differs.

Note the same 90-day log-retention caveat as PR #476: this output is transcribed here because the run's logs expire around 2026-11-08.

The proof branch is kept — the commit it cites is reachable only from it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant