-
Notifications
You must be signed in to change notification settings - Fork 10
Pin Semgrep CI image by digest and scope security-events to the upload job (CWE-829) #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Keeps the digest/SHA pins in .github/workflows/ from going stale. | ||
| # | ||
| # NOTE: Dependabot cannot bump the `container: image:` digest in a workflow file — its | ||
| # `docker` ecosystem only parses Dockerfiles, Kubernetes manifests and Helm values | ||
| # (dependabot/dependabot-core#5819), and `github-actions` only covers `uses:` refs. | ||
| # The Semgrep image digest in Semgrep.yml must therefore be refreshed manually (the | ||
| # command is in a comment next to the pin), or by adopting Renovate, which does support | ||
| # workflow container digests. | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| open-pull-requests-limit: 5 | ||
| # Don't propose a version the day it is published: a compromised or hijacked release | ||
| # is usually caught and yanked within a few days. Same reasoning as the digest pin. | ||
| cooldown: | ||
| default-days: 7 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [blocking] The two component findings this change closes are still unrouted on the tracker — including the one whose remediation this file is. The change itself is correct and I could verify every claim behind it (see the review summary). The gap is on the tracker side: the finding that asks for the digest pin on this exact file is still sitting in Evidence
Fix
No change to this diff is needed for either.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — both siblings are routed on the tracker now, not just referenced from the parent. Chain-breaker (LOC-6925 · F-009) — assigned to the operator, moved to Sibling (LOC-6927 · F-011) — assigned, moved to Three corrections to that ticket went in with it, since a human triaging it would otherwise inherit them: its score disagrees with its own vector (5.9 vs 6.5); its data-flow step 3 rests on the dead Travis Also recorded the won't-do as a row in You were right that a sentence on the parent isn't a routing record. Agreed and fixed — no change to this diff. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,16 +18,25 @@ permissions: | |
| jobs: | ||
| semgrep: | ||
| # User definable name of this GitHub Actions job. | ||
| # This job runs third-party code (the Semgrep container), so it is granted | ||
| # `contents: read` only. SARIF upload — which needs `security-events: write` — is | ||
| # deliberately isolated in the `upload-sarif` job below, so a compromised image | ||
| # cannot write to the repository's code-scanning dashboard. | ||
| permissions: | ||
| contents: read # for actions/checkout to fetch code | ||
| security-events: write # for github/codeql-action/upload-sarif to upload SARIF results | ||
| name: semgrep/ci | ||
| # If you are self-hosting, change the following `runs-on` value: | ||
| name: semgrep/ci | ||
| # If you are self-hosting, change the following `runs-on` value: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| container: | ||
| # A Docker image with Semgrep installed. Do not change this. | ||
| image: returntocorp/semgrep:1.166.0 | ||
| # Pinned by immutable digest, not by tag: a tag (even a version tag) can be | ||
| # re-pointed at new content upstream, which would silently execute unreviewed | ||
| # third-party code in this runner on the next scheduled run. | ||
| # Digest below == returntocorp/semgrep:1.166.0 (multi-arch index, pushed 2026-06-11). | ||
| # To refresh the pin (and update this comment): | ||
| # docker manifest inspect returntocorp/semgrep:<version> -v | grep -m1 Digest | ||
| image: returntocorp/semgrep@sha256:c180f0c93a17b420c0af5006214a29d3c747c5459c732b740191adf657dd0068 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [blocking] The completion comment posted on the parent ticket skips the plain-English summary and is free-form prose instead of the standard structure. (Anchored here because this line is the change the comment describes — the finding itself is tracker-side, not in this diff.) Every factual claim in it checks out. I re-derived all three CVSS scores from their own vectors (8.7 / 8.0 / 6.5 against the tickets' 8.4 / 7.5 / 5.9), so the flagged mismatch is real and not a v3.1-vs-v4.0 artifact of the same advisory; the digest confirms independently against Evidence — the posted comment opens straight into Concretely, the reader gets no one-liner telling them: anyone who could push to the Fix — edit the comment in place (same
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — edited in place on the same The comment now opens with One deviation from your instruction, flagged rather than done quietly: I did not keep the marker block as-is — I shrank it. It was ~4 KB of JSON that re-encoded the whole assessment/fix/testing prose. The skill's § Marker section makes that a hard size guard, not a preference: only While reflowing I also folded in what changed this round — the And thank you for re-deriving all three CVSS scores independently rather than taking mine; that mismatch claim is load-bearing for the ticket and it's worth more having been checked twice. |
||
| # Skip any PR created by dependabot to avoid permission issues: | ||
| if: (github.actor != 'dependabot[bot]') | ||
|
|
||
|
|
@@ -37,11 +46,46 @@ jobs: | |
| # Run the "semgrep ci" command on the command line of the docker image. | ||
| - run: semgrep ci --sarif --output=semgrep.sarif | ||
| env: | ||
| # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. | ||
| # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. | ||
| SEMGREP_RULES: p/default # more at semgrep.dev/explore | ||
|
|
||
| # Hand the SARIF to the upload job as an artifact. `semgrep ci` exits non-zero when | ||
| # it has blocking findings, so this must run even on failure. | ||
| - name: Upload SARIF as a workflow artifact | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||
| with: | ||
| name: semgrep-sarif | ||
| path: semgrep.sarif | ||
| if-no-files-found: error | ||
| if: always() | ||
|
|
||
| # Separate job so that `security-events: write` is never held by the job running the | ||
| # third-party Semgrep image. This job runs no third-party code beyond first-party | ||
| # GitHub actions, all digest-pinned. | ||
| upload-sarif: | ||
| name: Upload SARIF to GitHub Advanced Security Dashboard | ||
| needs: semgrep | ||
| if: always() && (github.actor != 'dependabot[bot]') | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| security-events: write # for github/codeql-action/upload-sarif to upload SARIF results | ||
|
|
||
| steps: | ||
| # Tolerate a missing artifact. If the semgrep job died BEFORE `semgrep ci` ran — a | ||
| # container-pull failure, or checkout failing — no SARIF was ever written, and this | ||
| # job should not add a second red pointing at artifact download when the real cause | ||
| # is upstream. The container job's own failure already tells that story. | ||
| # (`semgrep ci` exiting 1 on blocking findings is the normal case: the SARIF exists, | ||
| # the artifact uploads, and the upload below runs as usual.) | ||
| - name: Download SARIF artifact | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] When the The interesting failure mode — The uncovered case is a failure upstream of Not a correctness problem — nothing is silently lost, and the dashboard behaviour is unchanged. Purely about not sending someone debugging a registry outage to a download step. If you want it tighter, either gate the job on the artifact-producing step having run, or let the download tolerate absence: - name: Download SARIF artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: semgrep-sarif
continue-on-error: truewith the upload step then guarded on the file existing. Equally fine to leave as-is and let the container job's own red tell the story.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Taken — Went with your second suggestion, both halves of it: - name: Download SARIF artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: semgrep-sarif
continue-on-error: true
- name: Upload SARIF file for GitHub Advanced Security Dashboard
uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0
with:
sarif_file: semgrep.sarif
if: hashFiles('semgrep.sarif') != ''The guard on the upload step is what makes I preferred this over gating the job on a Net effect is what you asked for — an upstream failure now shows exactly one red job, the container job, which is the one that can explain itself. The case that mattered is unchanged: No new CI evidence for the new path, and I'd rather say so than imply otherwise: forcing a container-pull failure would mean pushing a deliberately bogus digest, and I didn't think a broken commit in the PR history was worth it. The reasoning above is the argument; the normal path stays covered by the runs already on the PR. |
||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | ||
| with: | ||
| name: semgrep-sarif | ||
| continue-on-error: true | ||
|
|
||
| - name: Upload SARIF file for GitHub Advanced Security Dashboard | ||
| uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 | ||
| with: | ||
| sarif_file: semgrep.sarif | ||
| if: always() | ||
| if: hashFiles('semgrep.sarif') != '' | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] This does what I asked for, and it works — but it's worth naming the trade it makes, since I'm the one who suggested it. Verified on the new head: run 31607926461 — The trade: I've reasoned that path rather than exercised it — the PR runs only cover the happy path, and I wouldn't ask you to force an artifact-download failure to prove it. Low stakes either way: the next push or the 6am cron re-uploads, so a missed upload self-heals within a day, and code scanning is not the enforcement gate here. Leaving it as-is is a defensible call. If you'd rather keep the loud signal for the case that deserves it, gating on the producing step is the discriminator — e.g. give the |
||
Uh oh!
There was an error while loading. Please reload this page.