From 1fc44b33cf3fff2fa255c967b38ea7ee97363f37 Mon Sep 17 00:00:00 2001 From: Sourav Kunda Date: Wed, 12 Aug 2026 18:35:07 +0530 Subject: [PATCH 1/3] Pin Semgrep CI image by digest and drop security-events from the container job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Semgrep workflow runs a third-party container on a daily cron with `security-events: write` and the repository checked out. Two hardening changes (CWE-829, Inclusion of Functionality from Untrusted Control Sphere): 1. Pin `returntocorp/semgrep` by immutable digest instead of the `1.166.0` tag. A version tag on Docker Hub is still mutable — it can be re-pointed at new content upstream, and the next scheduled run would execute unreviewed code with no PR gate. The digest is the same image that tag resolves to today (multi-arch index, pushed 2026-06-11), so this is a no-op for behaviour. 2. Move the SARIF upload into its own job. `security-events: write` is now held only by a job that runs no third-party code; the container job keeps `contents: read`. If the image is ever compromised it can no longer forge entries in the code-scanning dashboard. The SARIF crosses between jobs as a workflow artifact, uploaded with `if: always()` because `semgrep ci` exits non-zero when it has blocking findings. Also adds a Dependabot config for `github-actions` so the action SHA pins do not go stale. Dependabot cannot bump a workflow `container:` digest (dependabot/dependabot-core#5819), so the refresh command for the image pin is recorded next to it. Ref: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions Co-Authored-By: Claude Opus 5 (1M context) --- .github/dependabot.yml | 15 +++++++++++ .github/workflows/Semgrep.yml | 48 ++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4eee0f5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# 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 diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml index 95c5710..fb291d9 100644 --- a/.github/workflows/Semgrep.yml +++ b/.github/workflows/Semgrep.yml @@ -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: -v | grep -m1 Digest + image: returntocorp/semgrep@sha256:c180f0c93a17b420c0af5006214a29d3c747c5459c732b740191adf657dd0068 # Skip any PR created by dependabot to avoid permission issues: if: (github.actor != 'dependabot[bot]') @@ -37,11 +46,38 @@ 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: + - name: Download SARIF artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: semgrep-sarif + - 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() \ No newline at end of file From 8b08eb92f4af74894c81f60bb28bcc17f74836e5 Mon Sep 17 00:00:00 2001 From: Sourav Kunda Date: Wed, 12 Aug 2026 18:38:55 +0530 Subject: [PATCH 2/3] Add a 7-day cooldown to the Dependabot config Flagged by this repo's own Semgrep scan on the previous commit (package_managers.dependabot.dependabot-missing-cooldown): without a cooldown, Dependabot proposes a version the moment it is published, which is exactly the window a hijacked release exploits. Compromised releases are typically yanked within a few days, so wait 7. Co-Authored-By: Claude Opus 5 (1M context) --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4eee0f5..5dfe9ea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,7 @@ updates: 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 From 233e0b041b4c8fc38520b39bbb6622cfebdaa23e Mon Sep 17 00:00:00 2001 From: Sourav Kunda Date: Wed, 12 Aug 2026 20:08:41 +0530 Subject: [PATCH 3/3] Don't turn the upload job red when no SARIF was ever produced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review nit: if the container job dies before `semgrep ci` runs — a container-pull failure, or checkout failing — no SARIF is written, and the upload job then went red on "artifact not found", pointing a debugger at the download step instead of at the real upstream cause. Two reds where the pre-split workflow showed one. Tolerate the missing artifact and skip the upload when there is no file. The normal case is unaffected: `semgrep ci` exiting 1 on blocking findings still writes the SARIF, still uploads it, and still reaches the dashboard. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/Semgrep.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml index fb291d9..c2a7875 100644 --- a/.github/workflows/Semgrep.yml +++ b/.github/workflows/Semgrep.yml @@ -72,12 +72,20 @@ jobs: 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 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') != ''