diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 00edf90b..cd097dcd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -22,9 +22,16 @@ on: - cron: '23 4 * * 0' workflow_dispatch: -# Cancel in-progress runs for the same PR when a new commit lands. +# Cancel in-progress runs for the same commit when a new event lands. +# Key on commit SHA (PR head when triggered by pull_request, pushed SHA +# otherwise) instead of github.ref. With `push: branches: ['**']` and +# `pull_request: branches: [main]` both active, a single PR commit +# triggers two runs — one for the push event (refs/heads/) and +# one for the PR event (refs/pull/N/merge). Keying on ref leaves both +# in distinct groups, so neither cancels the other. SHA-keyed grouping +# dedups them. Codex round-2 P2 catch on PR #264. concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.sha }} cancel-in-progress: true permissions: diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index b8d332a4..1ac13386 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -39,9 +39,13 @@ on: - cron: '17 7 * * 1' workflow_dispatch: -# Cancel in-progress runs for the same PR when a new commit lands. +# Cancel in-progress runs for the same commit when a new event lands. +# SHA-keyed (PR head SHA or push SHA) instead of ref-keyed, so a single +# PR commit doesn't run twice — once for the push:[**] event and once +# for the pull_request event with different github.ref values. Same +# fix as codeql.yml. Codex round-2 P2 catch on PR #264. concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.sha }} cancel-in-progress: true permissions: diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index e7c0ad4b..81347629 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -362,7 +362,15 @@ jobs: # docs/requirements.txt (generated via `pip-compile --generate-hashes` # against docs/requirements.in). Closes Scorecard Pinned-Dependencies # warning on this line. - docker run --rm -v $PWD/docs:/docs -w /docs python:3.9.18 sh -c "pip install --require-hashes -r requirements.txt && PATH=\$PATH:~/.local/bin mkdocs build" + # + # python:3.12-slim, SHA-pinned digest. Required because the + # patched versions of `requests` (>=2.33.0) and `urllib3` + # (>=2.7.0) used to close GHSA-gc5v / -mf9v / -qccp transitive- + # dep advisories declare `requires_python: >=3.10`. Python 3.9 + # base would force keeping the vulnerable transitives. + # Digest is multi-platform (OCI image index) — covers amd64 + # / arm64 / arm/v5 / arm/v7 / 386 / ppc64le / riscv64 / s390x. + docker run --rm -v $PWD/docs:/docs -w /docs python@sha256:401f6e1a67dad31a1bd78e9ad22d0ee0a3b52154e6bd30e90be696bb6a3d7461 sh -c "pip install --require-hashes -r requirements.txt && PATH=\$PATH:~/.local/bin mkdocs build" - name: upload docs artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/docs/requirements.in b/docs/requirements.in index 4f894161..4a44b120 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -2,3 +2,13 @@ mkdocs~=1.5.3 mkdocs-material~=9.4.8 mkdocs-material-extensions~=1.3 mkdocs-mermaid2-plugin~=1.1.1 + +# Transitive-dep CVE pins surfaced by Scorecard after PR #264: +# - GHSA-gc5v-m9x4-r6x2 (CVE-2026-25645) - requests < 2.33.0 (insecure temp file reuse) +# - GHSA-mf9v-mfxr-j63j (CVE-2026-44432) - urllib3 < 2.7.0 (decompression-bomb bypass) +# - GHSA-qccp-gfcp-xxvc (CVE-2026-44431) - urllib3 < 2.7.0 (cross-origin header leak) +# Pinning the patched floors here forces pip-compile to resolve to safe +# versions. Both deps are reachable through mkdocs-material's plugin +# chain at docs-build time only. +requests>=2.33.0 +urllib3>=2.7.0 diff --git a/docs/requirements.txt b/docs/requirements.txt index 4fb522fd..74756e76 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --generate-hashes --output-file=requirements.txt requirements.in +# pip-compile --allow-unsafe --cert=None --client-cert=None --generate-hashes --index-url=None --output-file=requirements.txt --pip-args=None requirements.in # babel==2.18.0 \ --hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \ @@ -167,12 +167,6 @@ idna==3.15 \ --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc # via requests -importlib-metadata==8.7.1 \ - --hash=sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb \ - --hash=sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 - # via - # markdown - # mkdocs jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 @@ -553,10 +547,11 @@ regex==2026.1.15 \ --hash=sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5 \ --hash=sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e # via mkdocs-material -requests==2.32.5 \ - --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ - --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed # via + # -r requirements.in # mkdocs-material # mkdocs-mermaid2-plugin six==1.17.0 \ @@ -573,10 +568,12 @@ typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 # via beautifulsoup4 -urllib3==2.6.3 \ - --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ - --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 - # via requests +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 + # via + # -r requirements.in + # requests watchdog==6.0.0 \ --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ @@ -609,10 +606,6 @@ watchdog==6.0.0 \ --hash=sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c \ --hash=sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 # via mkdocs -zipp==3.23.1 \ - --hash=sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc \ - --hash=sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==82.0.1 \ diff --git a/pkg/security/cache_fuzz_test.go b/pkg/security/cache_fuzz_test.go index 1b9ff8c6..28d596fe 100644 --- a/pkg/security/cache_fuzz_test.go +++ b/pkg/security/cache_fuzz_test.go @@ -84,14 +84,21 @@ func FuzzVerifyAndExtract(f *testing.F) { keyA := CacheKey{Operation: "sbom", ImageDigest: "sha256:A", ConfigHash: "h1"} keyB := CacheKey{Operation: "scan-grype", ImageDigest: "sha256:B", ConfigHash: "h2"} - future := time.Now().Add(time.Hour) - past := time.Now().Add(-time.Hour) + // Anchor seed timestamps to the SAME fixed clock the fuzz body uses + // (fuzzNow below), not the real wall-clock. Earlier version used + // `time.Now().Add(-time.Hour)` for the "expired" seed, but the fuzz + // body's `now` is fixed to time.Unix(1_700_000_000, 0) (Nov 2023); + // any real-clock "past" is years AFTER that, so the expired-seed + // branch was never exercised by seed #3. Codex round-2 P2 catch. + fuzzNow := time.Unix(1_700_000_000, 0).UTC() + future := fuzzNow.Add(time.Hour) + past := fuzzNow.Add(-time.Hour) // Seed 1: a known-good signed entry, requested with the matching key. f.Add(validSignedBytes(f, cache, keyA, []byte("payload-A"), future), "sbom", "sha256:A", "h1") // Seed 2: same valid bytes, requested with a DIFFERENT key — must miss. f.Add(validSignedBytes(f, cache, keyA, []byte("payload-A"), future), "scan-grype", "sha256:B", "h2") - // Seed 3: valid entry but already expired — must miss. + // Seed 3: valid entry but already expired vs fuzzNow — must miss. f.Add(validSignedBytes(f, cache, keyA, []byte("payload-A"), past), "sbom", "sha256:A", "h1") // Seed 4: a tampered Data payload (MAC no longer matches). tampered := tamperData(f, cache, keyA, []byte("orig"), []byte("evil"), future)