Skip to content

chore(deps): Dependabot digest rotation for pinned Semgrep image [DEVA11Y-476] - #38

Merged
Crash0v3rrid3 merged 4 commits into
mainfrom
chore/DEVA11Y-476-dependabot-semgrep-rotation
Aug 28, 2026
Merged

chore(deps): Dependabot digest rotation for pinned Semgrep image [DEVA11Y-476]#38
Crash0v3rrid3 merged 4 commits into
mainfrom
chore/DEVA11Y-476-dependabot-semgrep-rotation

Conversation

@Crash0v3rrid3

@Crash0v3rrid3 Crash0v3rrid3 commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

What & why (DEVA11Y-476 / chain DEVA11Y-485)

The image-pin chain-breaker for C-001 is already merged (PR #15) — the Semgrep CI image is pinned to an immutable @sha256 digest. This PR is optional hardening on top: keep pins current without silently rotting.

Pivot after review

The original attempt used Dependabot's docker ecosystem over /.github/workflows to bump the image digest. That cannot work: Dependabot's docker ecosystem only discovers Dockerfiles/Containerfiles, Kubernetes manifests, Helm values and Compose files — not container: image refs in workflow files (dependabot-core#5819, still open). It would have produced a weekly failing Dependabot job and never rotated the pin. Reworked into two mechanisms that do work:

  1. .github/dependabot.ymlgithub-actions ecosystem. Rotates the action uses: pins across the workflows (several are ~3 years stale, e.g. actions/checkout@v3.5.3, codeql-action@v2.20.0 — v2 retired Jan 2025). directory: "/", weekly. This is real, supported rotation.
  2. .github/workflows/semgrep-image-pin-drift.yml (new). Read-only (contents: read) scheduled + workflow_dispatch job that compares the pinned returntocorp/semgrep@sha256 digest against :latest via the Docker registry v2 API and fails on drift, so a human bumps the image digest deliberately after reviewing it. For the C-001 threat model (poisoned upstream tag), human-in-loop is preferable to auto-adopting whatever latest resolves to. Verified against the current pin — correctly reports DRIFTED today (f682953… vs f1f7b71…).

Notes

  • Cooldown key retained but INERTcooldown is not supported for the github-actions ecosystem (only docker/npm/pip/… support it), so it applies no release-age protection to action bumps; it is kept solely to satisfy Semgrep's dependabot-missing-cooldown rule and is commented as inert in the file. Real cooldown for actions would need Renovate's minimumReleaseAge.
  • Dropped the security label (undefined in this repo → silently ignored) and the redundant open-pull-requests-limit.
  • Semgrep.yml's if: github.actor != 'dependabot[bot]' guard is left as-is; it's moot now that Dependabot no longer bumps the image.
  • DEVA11Y-476 closure should not rely on Dependabot rotating the image — it can't. The digest is covered by the drift alarm; action pins are covered by Dependabot.

🤖 Generated with Claude Code


Round 2 (abc0e1d): drift alarm reworked from "differs from :latest" to "pinned image older than 45 days" (actionable, not weekly noise) and made bash -e-safe; Semgrep image pin bumped to the current :latest digest sha256:f1f7b71861c7b28b6e0f661225a2c4f58a484f5d0f182465c6d6b3b22f972ade so the alarm lands green; Semgrep.yml job-level dependabot guard scoped to only the upload-sarif step so Dependabot PRs still get scanned.

DEVA11Y-476 note: closure does not rely on Dependabot rotating the container image (it cannot). The image digest is covered by the freshness alarm + manual bump; the action uses: pins are rotated by Dependabot. The cooldown key is inert for github-actions and provides no protection.

…VA11Y-476)

The Semgrep workflow container image is already pinned by immutable @sha256
digest (PR #15), which is the DEVA11Y-476 chain-breaker for the C-001 chain
(DEVA11Y-485: CI image compromise -> mutable main -> SPM plugin RCE). A static
digest, however, never receives upstream security patches — the current pin
already lags returntocorp/semgrep:latest.

Add .github/dependabot.yml with a `docker` ecosystem entry over
/.github/workflows so Dependabot bumps the pinned digest to the newest build on
a weekly cadence, keeping immutability without freezing the image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread .github/dependabot.yml Fixed
Adds a release-age cooldown so Dependabot does not adopt a freshly-published
returntocorp/semgrep digest immediately — a poisoned-image would otherwise be
pulled in within a day of publication, the exact window digest-pinning is meant
to defend (DEVA11Y-476 / chain DEVA11Y-485). Also resolves the semgrep/ci
finding flagging the update config for lacking a minimum release age.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Crash0v3rrid3
Crash0v3rrid3 marked this pull request as ready for review August 27, 2026 10:49
@Crash0v3rrid3
Crash0v3rrid3 requested a review from a team as a code owner August 27, 2026 10:49

@nmnishant-browserstack nmnishant-browserstack 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.

Review

The intent is right and the drift is real — I reproduced it (returntocorp/semgrep:latest = sha256:f1f7b71…, last pushed 2026-08-25, vs the pinned sha256:f682953…). But as written this config will not rotate the pin, and it will leave a permanently failing Dependabot run behind. Details and a working alternative below.


🔴 Blocker — Dependabot's docker ecosystem cannot see container: images in workflow files

package-ecosystem: docker + directory: "/.github/workflows" finds zero dependency files here. Two independent confirmations:

1. Upstream doesn't support it yet. dependabot-core#5819 — "Update container image references in GitHub Action workflows" is still open (filed 2022-09-30, last activity 2026-06-03, where a commenter is holding off stalebot with "This remains a feature that I would find very valuable… important as one measure to reduce the risks of supply chain attacks — as well as facilitating the use of some other measures… such as cooldown" — exactly this PR's goal). The github-actions ecosystem only handles uses: with GitHub-repo syntax; docker:// refs and registry URLs are explicitly unsupported.

2. The file fetcher provably skips Semgrep.yml. From dependabot-core/docker/lib/dependabot/docker/file_fetcher.rb and …/shared/shared_file_fetcher.rb:

DOCKER_REGEXP = /dockerfile|containerfile/i          # file_fetcher.rb
YAML_REGEXP   = /^[^\.].*\.ya?ml$/i                  # shared_file_fetcher.rb

def likely_kubernetes_resource?(resource)
  resource.is_a?(::Hash) && resource.key?("apiVersion") && resource.key?("kind")
end
HELM_REGEXP = /values[\-a-zA-Z_0-9.]*\.ya?ml$/i      # utils/helpers.rb

Semgrep.yml matches YAML_REGEXP, so it gets fetched — then dropped by correctly_encoded_yamlfiles, because a workflow has no apiVersion/kind and isn't a values*.yaml Helm file. With fetched_files empty, raise_appropriate_error fires:

raise Dependabot::DependencyFileNotFound.new(
  File.join(directory, "Dockerfile"),
  "No Dockerfiles nor Kubernetes YAML found in #{directory}"
)

So the observable outcome isn't a silent no-op — it's a red Dependabot update job every week under Insights → Dependency graph → Dependabot, and no rotation. The green .github/dependabot.yml check on this PR only validates schema, not that any dependency file is discoverable.

The docker ecosystem parses Dockerfiles/Containerfiles, Kubernetes manifests + Helm and Docker Compose — none of which exist in this repo.

Credit where it's due: the two design choices that look wrong are actually correct, so they need no change once discovery is solved. I checked docker/lib/dependabot/docker/update_checker.rb: a bare digest pin with no tag is supported and resolves against latest (fetch_latest_tagreturn Tag.new(latest_digest) if version_tag.digest?; latest_digestdigest_of("latest")), which matches the docker manifest inspect returntocorp/semgrep:latest comment in the workflow. And cooldown does gate that path — digest_requirement_up_to_date? has a dedicated branch: "Pure digest pin (no tag): the proposed digest resolves from the latest tag, so gate it on the same cooldown window"digest_within_cooldown?("latest") using cooldown.default_days. default-days is also the only cooldown key Docker supports (semver-*-days is unsupported for Docker), which is exactly what you used. Also non-issue: returntocorp/semgrep is still actively rebuilt (1.175.0 pushed 2026-08-26) and :latest is digest-identical to semgrep/semgrep:latest, so the deprecated-looking org name is fine.


🟠 The rotation PR would never exercise the new image

Semgrep.yml:34:

if: (github.actor != 'dependabot[bot]')

That's the only job in the workflow, so a Dependabot-authored digest bump skips Semgrep entirely — the new image is adopted without ever having been run. Worth fixing under whatever mechanism you land on, since it's the whole safety value of a bump PR. Note the same hole exists for a self-rolled bumper: a PR created with the default GITHUB_TOKEN doesn't trigger workflows at all. Options: allow the actor when the diff only touches the pin, or dispatch the check manually on those PRs.


🟠 The ecosystem entry that is supported, and that this repo needs, is missing

github-actions version updates work today, and the action pins in this repo are far more stale than the Semgrep image — including in the very file this PR is trying to keep current:

Pin Committed Current
actions/checkout@c85c95e (v3.5.3) — Semgrep.yml:38 2023-06-09 v7.0.1 (2026-07-20)
github/codeql-action/upload-sarif@6c089f5 (v2.20.0) — Semgrep.yml:46 2023-06-13 v4.37.9
actions/checkout@11bd719 (v4.2.2) — other 2 workflows 2024-10-23 v7.0.1

CodeQL Action v2 was retired in January 2025 (changelog) — no updates, no support, "workflows using it may eventually break". checkout@v3 is three majors back. The PR's own argument ("a static pin never receives upstream security patches") applies to these with more force, and here Dependabot can actually act on it:

  - package-ecosystem: "github-actions"
    directory: "/"          # note: "/" for this ecosystem — it searches .github/workflows itself
    schedule:
      interval: "weekly"
    cooldown:
      default-days: 7
    commit-message:
      prefix: "chore(deps)"
    labels:
      - "dependencies"

🟡 Nits

  • labels: ["dependencies", "security"] — neither label exists in this repo. Dependabot auto-creates dependencies, but per the docs "if any of these labels is not defined in the repository, it is ignored" — so security will be silently dropped. Create it or drop the line.
  • open-pull-requests-limit: 5 is already the default; redundant for a single image.
  • PR body says "21 lines"; the file is 26.

Suggested path forward

  1. Replace the docker entry with the github-actions entry above — real, supported rotation for pins that are three years stale.
  2. For the Semgrep image, drop auto-rotation and add a drift alarm instead. It needs no write permissions, keeps a human in the loop on what digest gets adopted (better for the C-001 threat model than auto-adopting whatever latest resolves to), and can't silently stop working. Verified working against this repo's current pin — it correctly reports DRIFTED:
name: Semgrep image pin drift
on:
  schedule: [{ cron: '0 7 * * 1' }]
  workflow_dispatch:
permissions:
  contents: read
jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<sha>  # v4.2.2
      - name: Compare pinned digest against returntocorp/semgrep:latest
        run: |
          set -euo pipefail
          pinned=$(grep -oE 'returntocorp/semgrep@sha256:[0-9a-f]{64}' \
            .github/workflows/Semgrep.yml | cut -d@ -f2)
          token=$(curl -fsS "https://auth.docker.io/token?service=registry.docker.io&scope=repository:returntocorp/semgrep:pull" | jq -r .token)
          latest=$(curl -fsSI -H "Authorization: Bearer $token" \
            -H 'Accept: application/vnd.oci.image.index.v1+json' \
            -H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
            "https://registry-1.docker.io/v2/returntocorp/semgrep/manifests/latest" \
            | tr -d '\r' | awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}')
          [ "$pinned" = "$latest" ] || {
            echo "::error::Semgrep image pin drifted ($pinned != $latest) — bump the digest in .github/workflows/Semgrep.yml"
            exit 1; }

If you'd rather keep it fully automated, Renovate does handle container:/services: image refs in workflow files, and would cover both the image and the action pins in one tool — heavier to adopt, but it's the one off-the-shelf thing that does what this PR set out to do.
3. Either way, update the DEVA11Y-476 notes: "Dependabot rotates the pin" isn't achievable with Dependabot alone today, and the ticket shouldn't close on a config that can't fire.

Happy to push either variant as a commit on this branch if that's easier.

✨ ai-drafted via Claude — reviewed by Nishant

…grep image

Dependabot's `docker` ecosystem cannot discover a `container:` image ref in a
workflow file (dependabot-core#5819), so the previous `docker`/`/.github/workflows`
config would never rotate the pin and would fail a weekly Dependabot job. Pivot:

- dependabot.yml: replace the unusable `docker` entry with a `github-actions`
  entry (directory "/", weekly, cooldown default-days 7) — real, supported
  rotation for the action `uses:` pins here, several of which are 3 years stale.
  Drop the undefined `security` label and the redundant open-pull-requests-limit.
  Cooldown retained (also clears the semgrep/ci dependabot-missing-cooldown rule).
- Add semgrep-image-pin-drift.yml: read-only scheduled/dispatch job that alarms
  (fails) when the pinned returntocorp/semgrep@sha256 digest drifts from :latest,
  keeping a human in the loop on the image bump (better for the C-001 threat model
  than auto-adopting latest).

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

Copy link
Copy Markdown
Collaborator Author

@nmnishant-browserstack — all addressed in ca95c3a. Thanks, the docker-ecosystem call was spot on.

🔴 docker ecosystem can't see container: refs → removed it. Replaced with a github-actions entry (directory: "/", weekly, cooldown: default-days: 7) — real rotation for the stale action pins.

🟠 image never re-run on a bump / actor guard → moot now: Dependabot no longer bumps the image. Instead added .github/workflows/semgrep-image-pin-drift.yml — read-only (contents: read) scheduled + workflow_dispatch job, your registry-v2 approach, that fails on drift so the digest bump stays human-in-loop (better for C-001 than auto-adopting latest). Verified against the current pin: correctly reports DRIFTED (f682953… vs f1f7b71…). Left Semgrep.yml's if: github.actor != 'dependabot[bot]' untouched.

🟠 github-actions ecosystem missing → that's now the sole Dependabot entry, so checkout@v3.5.3 / codeql-action@v2.20.0 (v2 retired) etc. get bumped.

🟡 nits → dropped the undefined security label (kept dependencies); removed the redundant open-pull-requests-limit; PR body rewritten (the "21 lines" figure is gone with it). Cooldown kept — also keeps the dependabot-missing-cooldown finding cleared.

DEVA11Y-476 note → corrected in the PR body: closure does not rely on Dependabot rotating the image (it can't); the digest is covered by the drift alarm, action pins by Dependabot.

All 9 checks green on ca95c3a, incl. semgrep/ci. Renovate (which does handle container: refs) noted as the heavier all-in-one alternative if we later want the image fully automated.

✨ ai-drafted via Claude — reviewed by Rishabh

@nmnishant-browserstack nmnishant-browserstack 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.

Review — ca95c3a

The pivot lands the main thing: the docker ecosystem entry is gone, so the blocker from the last round is resolved, and the drift check's happy path genuinely works — I ran it against this branch and it correctly resolves both digests and annotates the drift.

Four things below. Two are cheap defects, one is a claim in the PR body that isn't true, and one is a hole the pivot changed the shape of but didn't close.


🔴 1. cooldown is not supported for github-actions — the block is inert

GitHub's cooldown support table lists Bazel, Bundler, Bun, Cargo, Composer, Conda, Deno, Devcontainers, Docker, Docker Compose, Dotnet SDK, Elm, Gitsubmodule, Gomod, Gradle, Helm, Hex, Julia, Maven, Nix flakes, NPM/Yarn, NuGet, OpenTofu, Pip, pre-commit, Pub, Rust toolchain, sbt, Swift, Terraform, UV, vcpkg. github-actions is absent (options reference). Ironically it was supported on the docker entry you just removed.

The trap is the same one that sank the first attempt: the green .github/dependabot.yml check is schema validation only, so an unsupported-but-well-formed key passes. Net effect — the PR body says "Cooldown retained — also clears the semgrep/ci dependabot-missing-cooldown finding." The finding does clear. The protection doesn't exist. That's strictly worse than not having it, because the alert that would have told you is now silenced.

Not asking you to rip it out — keep the key if it keeps semgrep/ci green, but comment it as ecosystem-inert and fix the claim in the PR body and the DEVA11Y-476 notes, so the next person adding a cooldown-supporting ecosystem doesn't inherit a config that looks protected. If cooldown for actions genuinely matters, Renovate's minimumReleaseAge is the lever that works today.

🔴 2. Both error branches in the drift job are dead code under GHA's bash -e

No shell: is set, so GitHub runs the step as bash -e {0}, and set -uo pipefail doesn't clear that injected -e. pinned=$(grep … | head -n1 | cut …) is a simple command, so a non-matching grep aborts the script before if [ -z "$pinned" ] is reached. Ran it both ways against this branch:

happy path      → pinned/latest printed, ::error:: drift annotation, exit 1   ✅
pin removed     → exit 1, NO ::error:: annotation at all                      ❌
registry down   → exit 7, NO ::error:: annotation at all                      ❌

So a malformed Semgrep.yml or a Docker Hub blip produces a bare red square with no explanation — on a scheduled job, where that annotation is the only artifact anyone will ever see. This is exactly the trap you fixed in #37 66249fe, and the same || true capture from sidecar-availability fixes it:

pinned=$(grep -oE '' .github/workflows/Semgrep.yml | head -n1 | cut -d@ -f2 || true)
token=$(curl -fsS "" | jq -r .token || true)
latest=$(curl -fsSI … | awk '' || true)

Please add a [ -z "${token:-}" ] guard too — a registry auth failure currently has no diagnostic at all. || true also covers grep | head -n1 exiting 141 on SIGPIPE.

For the record this one is partly mine — the snippet I handed you last round had the same latent issue.

🟠 3. The alarm is red on arrival, and will be red most weeks

Drift exists right now (f682953… vs f1f7b71…), which you noted — so this job fails the first Monday after merge. And returntocorp/semgrep:latest moves roughly weekly: latest pushed 2026-08-25, 1.175.0 on 08-26, 1.174.0 on 08-20. So the cycle is: bump the pin → green for a few days → red again.

An alarm that is already failing when it merges, and returns to failing within a week of every fix, gets muted — and a muted supply-chain alarm is worse than none, because it reads as coverage. Two ways out, and I'd do both:

  • Bump the pin in this PR so it lands green. That's also the thing DEVA11Y-476 actually asks for.
  • Alarm on staleness, not on difference — fail only when the pinned digest is more than N days old, rather than whenever latest has moved at all. "Your pin is 90 days stale" is actionable; "upstream pushed yesterday" is not.

🟠 4. The dependabot[bot] guard isn't moot — it now defeats the entry you just added

The reply says the guard is moot "now that Dependabot no longer bumps the image." But the new github-actions entry bumps actions/checkout@c85c95e (v3.5.3) and github/codeql-action/upload-sarif@6c089f5 (v2.20.0) — and both of those live in Semgrep.yml itself, lines 38 and 46. if: (github.actor != 'dependabot[bot]') sits on that workflow's only job, so every Dependabot PR touching those pins skips semgrep entirely, and the bumped action merges without the workflow it lives in ever having run against it.

That's the same hole I flagged last round — the pivot changed what triggers it, not whether it's there. And it now fires more often, since actions rotate more than the image did. The guard's comment says it's about "permission issues", but the job has had an explicit permissions: block with security-events: write since before this PR — worth checking whether the original problem still exists, and if not, dropping the guard. Otherwise scope it to just the upload-sarif step rather than the whole job.


Checked and fine — no action

  • dependencies label doesn't exist in this repo (labels are bug, documentation, duplicate, enhancement, good first issue, help wanted, invalid, question, wontfix). Unlike the security label you dropped, dependencies is one of Dependabot's default labels and gets auto-created, so this one is safe.
  • directory: "/" is right for github-actions.
  • permissions: contents: read on the new workflow — correct, and no write scope anywhere.
  • Only two distinct action repos across all workflows, so skipping groups: is fine at this size.

One operational note

GitHub disables scheduled workflows after 60 days of repository inactivity, and a failed scheduled run only emails whoever last edited the cron — there's no team routing here. For a supply-chain alarm meant to outlive whoever wrote it, both are worth at least a comment in the file, and ideally an issue-creating step or a Slack ping.


Verdict

Changes requested (posted as a comment, not a formal block — say the word and I'll convert it). #2 and #4 are cheap and concrete; #1 is a one-line claim correction plus a comment; #3 is a design call I'd like made before this merges rather than after it's been ignored for two months. The core direction — Dependabot for what it can rotate, a drift alarm for what it can't — is right, and the docker-ecosystem blocker is properly resolved.

✨ ai-drafted via Claude — reviewed by Nishant

Comment thread .github/dependabot.yml
Comment thread .github/workflows/semgrep-image-pin-drift.yml Outdated
Resolves Nishant's second review on PR #38:

1. cooldown is inert for the github-actions Dependabot ecosystem — kept only
   to satisfy Semgrep's dependabot-missing-cooldown rule; documented as inert
   in dependabot.yml (no real release-age protection for action bumps).
2. Semgrep image drift workflow: made the intended-failure branches reachable
   under GHA's injected `bash -e` (|| true on the grep/token/latest command
   substitutions + explicit empty-token guard), same class of fix as #37
   66249fe. Verified: happy/stale/pin-removed/registry-down all annotate+exit.
3. Reworked the alarm from "differs from :latest" (red most weeks → muted) to
   "pinned image older than 45 days" (actionable). Bumped the Semgrep image
   pin to the current :latest digest so it lands green:
   f682953… -> f1f7b71861c7b28b6e0f661225a2c4f58a484f5d0f182465c6d6b3b22f972ade
   (created 2026-08-20, ~8 days old).
4. Removed the job-level `if: github.actor != 'dependabot[bot]'` guard on
   Semgrep.yml and scoped it to only the privileged upload-sarif step, so
   Dependabot PRs that bump the action pins living in Semgrep.yml still get
   scanned; only the security-events:write SARIF upload is skipped for them.

Also documented the scheduled-workflow ops caveat (60-day inactivity disable;
failed runs notify only the last cron editor) in the drift workflow.

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

Copy link
Copy Markdown
Collaborator Author

@nmnishant-browserstack — all four addressed in abc0e1d. Great catches, especially the inert-cooldown and the bash -e dead branches.

🔴 1. cooldown inert for github-actions → kept the key (removing it re-triggers dependabot-missing-cooldown), but it's now commented as ecosystem-INERT in dependabot.yml, and the PR body + DEVA11Y-476 note are corrected — no claim that it protects anything. Flagged Renovate's minimumReleaseAge as the lever that actually works for actions.

🔴 2. Drift job dead branches under injected bash -e → fixed the same way as #37 66249fe: || true on the grep|head|cut, token, and latest command substitutions, plus an explicit empty-token guard with its own ::error::. Verified every intended-failure path now annotates and exits non-zero:

case exit annotation
fresh pin (~8d) 0 ::notice:: age
stale pin (~112d) 1 ::error:: stale
pin removed 1 ::error:: no pin found
registry unreachable 1 ::error:: auth failure

🟠 3. Red-on-arrival / flapping → both fixes:

  • Bumped the Semgrep image pin to the current :latest: sha256:f682953…sha256:f1f7b71861c7b28b6e0f661225a2c4f58a484f5d0f182465c6d6b3b22f972ade (config created 2026-08-20, ~8 days old). Re-resolved live via the registry v2 API, not copied from the review.
  • Reworked the alarm from "differs from :latest" to "pinned image older than 45 days" (resolves the pinned digest's config created and compares age). "Your pin is N days stale" is actionable; "upstream pushed yesterday" isn't. Lands green today.

🟠 4. dependabot[bot] guard defeats the new github-actions entry → removed the job-level if on Semgrep.yml and scoped if: always() && github.actor != 'dependabot[bot]' onto only the upload-sarif step. The scan now runs on Dependabot PRs (which bump checkout/codeql-action, both living in this workflow); only the security-events: write SARIF upload is skipped for them.

Ops note → documented in the drift workflow: scheduled workflows disable after 60 days of repo inactivity and a failed run only notifies the last cron editor, so this alarm is a backstop, not a substitute for the committed pin. Left issue/Slack routing out to keep the job read-only, as you noted.

All 9 checks green on abc0e1d, incl. semgrep/ci (now running in the bumped image).

✨ ai-drafted via Claude — reviewed by Rishabh

@nmnishant-browserstack nmnishant-browserstack 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.

Review — abc0e1d ✅ Approving

Clean pass over the whole PR, and re-ran every round-2 finding rather than reading the reply. All four are genuinely fixed, and the reworked freshness job works end-to-end against the live registry.

Verified by execution

Happy path — lands green, as intended:

[index] → linux/amd64 child sha256:155f999d…
[config] sha256:32381bb4…
pinned digest: sha256:f1f7b71861c7b28b6e0f661225a2c4f58a484f5d0f182465c6d6b3b22f972ade
image created: 2026-08-20T15:31:17Z (7 days ago)
::notice::Pinned Semgrep image is 7 days old (<= 45); no action needed.
EXIT=0

The multi-arch descent works (the pinned digest is an OCI index, so the .manifests[] | select(linux/amd64) hop is load-bearing, not defensive), the config-blob fetch survives Docker Hub's cross-host redirect to the CDN with the bearer header intact, and the single-arch fallback (imgman="${man}") is the right shape for when it isn't an index.

🔴 2 — bash -e dead branches: fixed. Every intended-failure path now annotates and exits non-zero. This was the one that mattered most, since it's the difference between an actionable alarm and a bare red square:

case annotation exit
pin removed from Semgrep.yml ::error::Could not find a returntocorp/semgrep@sha256 pin 1
digest absent from registry ::error::Could not fetch the manifest for pinned digest… 1
registry auth unreachable ::error::Could not obtain a Docker registry auth token… 1
genuinely stale pin (real 64-day-old digest) ::error::Pinned Semgrep image is 64 days old (> 45)… 1

I tested the stale case with a real 64-day-old returntocorp/semgrep digest rather than a synthetic clock, so the age arithmetic is confirmed against actual registry metadata.

🔴 1 — inert cooldown: correctly handled. Keeping the key to hold dependabot-missing-cooldown green while documenting it as ecosystem-inert is the right call, and the in-file comment is unambiguous about it providing no release-age protection. PR body and the DEVA11Y-476 note now say the same thing. Nobody inherits a false belief here.

🟠 3 — red-on-arrival: fixed properly, both halves. The pin is bumped to sha256:f1f7b71…, which I re-resolved live — it matches returntocorp/semgrep:latest exactly, so it was re-derived rather than copied from my review. And semgrep/ci is green on this commit, which means the bumped digest is validated as a working CI container, not just a string. Switching the predicate from "differs from :latest" to "older than 45 days" is the substantive fix: 45 days against semgrep's ~weekly rebuild cadence gives roughly a six-week actionable window instead of near-permanent red.

🟠 4 — the Dependabot guard: correctly scoped. Parsed the YAML to confirm rather than eyeballing the diff — job-level if is gone, and always() && github.actor != 'dependabot[bot]' sits on the upload-sarif step alone. That's exactly right: Dependabot PRs get a read-only token, so only the security-events: write step needs skipping, and the scan itself now runs on the PRs that bump checkout/codeql-action — both of which live in this very workflow. The hole is closed.

Also confirmed: all three YAML files parse, all 9 checks green, and the ops caveat (60-day scheduled-workflow disablement, no team routing) is documented in the workflow header with the honest framing that this alarm is a backstop rather than the control itself.


Non-blocking nits — take or leave

  • Registry rate limiting can surface as a false alarm. The job now makes four anonymous Docker Hub calls per run (token → index → child manifest → config blob), up from two. GitHub runners share egress IPs and Docker Hub rate-limits anonymous pulls aggressively, so a 429 would land as ::error::…registry unreachable or digest gone — a red alarm that isn't about staleness. Weekly cadence makes this unlikely and the message is at least diagnosable; if it ever flaps, distinguishing 429 from a genuine miss would fix it.
  • Cosmetic: Semgrep.yml now has a double blank line where the job-level if: was removed.
  • date -u -d is GNU-only — fine on the pinned ubuntu-latest, just noting it if the runner ever changes.

Verdict: Approve

Three rounds in, this landed somewhere better than where it started. The original premise — "Dependabot rotates the pinned image" — turned out to be impossible, and rather than paper over that, the PR now says plainly what each mechanism does and does not cover: Dependabot rotates what it can (action pins), a freshness alarm backstops what it can't (the container digest), and the cooldown key is documented as decorative. The DEVA11Y-476 notes match reality, which is the part that outlives the diff.

Nice work on re-resolving the digest independently and on testing the failure paths rather than asserting them.

✨ ai-drafted via Claude — reviewed by Nishant

@Crash0v3rrid3
Crash0v3rrid3 merged commit e8ff791 into main Aug 28, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants