From c845be16c5769f0d1614f4b85466eea45c358132 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 03:54:04 +0900 Subject: [PATCH 1/5] chore(security): unify OSV Action v2.5.1 --- .github/workflows/security-scan.yml | 8 ++--- CHANGELOG.md | 3 ++ .../osv-scanner-action-single-version.md | 32 +++++++++++++++++++ tests/test_osv_scanner_action_pin_contract.py | 32 +++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 docs/doctoring/osv-scanner-action-single-version.md create mode 100644 tests/test_osv_scanner_action_pin_contract.py diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 29376269d..1554d563d 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -78,7 +78,7 @@ jobs: id: osv_base continue-on-error: true timeout-minutes: 8 - uses: google/osv-scanner-action/osv-scanner-action@a82132c0bd6c7261ffcb78e754c46c70ab57ad9a # v2.3.8 + uses: google/osv-scanner-action/osv-scanner-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 with: scan-args: | --format=json @@ -96,7 +96,7 @@ jobs: if: steps.osv_base.outcome == 'failure' continue-on-error: true timeout-minutes: 4 - uses: google/osv-scanner-action/osv-scanner-action@a82132c0bd6c7261ffcb78e754c46c70ab57ad9a # v2.3.8 + uses: google/osv-scanner-action/osv-scanner-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 with: scan-args: | --format=json @@ -117,7 +117,7 @@ jobs: id: osv_head continue-on-error: true timeout-minutes: 8 - uses: google/osv-scanner-action/osv-scanner-action@a82132c0bd6c7261ffcb78e754c46c70ab57ad9a # v2.3.8 + uses: google/osv-scanner-action/osv-scanner-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 with: scan-args: | --format=json @@ -135,7 +135,7 @@ jobs: if: steps.osv_head.outcome == 'failure' continue-on-error: true timeout-minutes: 4 - uses: google/osv-scanner-action/osv-scanner-action@a82132c0bd6c7261ffcb78e754c46c70ab57ad9a # v2.3.8 + uses: google/osv-scanner-action/osv-scanner-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 with: scan-args: | --format=json diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0ef8d44..2c11c39fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Aligned every central OSV-Scanner Action use to the official v2.5.1 commit, + preserving exact-base/head policy while applying the current namespace, + local-cache, and offline-vulnerability matching fixes. - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/docs/doctoring/osv-scanner-action-single-version.md b/docs/doctoring/osv-scanner-action-single-version.md new file mode 100644 index 000000000..d79d7c72e --- /dev/null +++ b/docs/doctoring/osv-scanner-action-single-version.md @@ -0,0 +1,32 @@ +# OSV-Scanner Action single-version boundary + +## Incident boundary + +The central dependency scan invokes `google/osv-scanner-action` four times for +base, head, and reporter evidence. All four uses remained on Action v2.3.8, +while the stale dependency pull request targeted v2.5.0 after v2.5.1 had become +the current official release. + +## Decision + +Pin every central `google/osv-scanner-action/osv-scanner-action` use to +`6e4298ebc4db23e847df9b2e2de2939d6f066c67`, the commit referenced by the +official v2.5.1 tag. That release preserves package namespaces, restores the +local database cache environment variable, and fixes offline vulnerability +matching (Google, 2026). + +GitHub documents that a full commit SHA is unique and immutable and should be +verified against the action repository (GitHub, n.d.). A repository-wide +contract therefore parses every central workflow occurrence, rejects malformed +pins or mismatched comments, and admits only the reviewed v2.5.1 SHA and tag. +Scan arguments, timeouts, permissions, exact-base/head comparison, reporter +gates, and fail-closed dependency policy are unchanged. + +## References + +GitHub. (n.d.). *Using pre-written building blocks in your workflow*. +Retrieved August 24, 2026, from +https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/find-and-customize-actions + +Google. (2026, August 17). *OSV-Scanner Action v2.5.1* [Software release]. +https://github.com/google/osv-scanner-action/releases/tag/v2.5.1 diff --git a/tests/test_osv_scanner_action_pin_contract.py b/tests/test_osv_scanner_action_pin_contract.py new file mode 100644 index 000000000..4b2132bb9 --- /dev/null +++ b/tests/test_osv_scanner_action_pin_contract.py @@ -0,0 +1,32 @@ +"""Keep every central OSV-Scanner Action use on one reviewed release.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +OSV_ACTION_SHA = "6e4298ebc4db23e847df9b2e2de2939d6f066c67" +OSV_ACTION_TAG = "v2.5.1" +_PIN = re.compile( + r"google/osv-scanner-action/osv-scanner-action@" + r"(?P[^\s]+)\s+#\s+(?Pv[^\s]+)" +) + + +def test_all_osv_scanner_actions_share_the_reviewed_current_release() -> None: + """Reject partial bumps, malformed refs, and stale OSV Action comments.""" + observed: set[tuple[str, str]] = set() + + for path in sorted((REPO_ROOT / ".github/workflows").glob("*.yml")): + for line_number, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), start=1 + ): + if "google/osv-scanner-action/osv-scanner-action@" not in line: + continue + match = _PIN.search(line) + assert match is not None, f"malformed OSV Action pin: {path}:{line_number}" + observed.add((match.group("sha"), match.group("tag"))) + + assert observed == {(OSV_ACTION_SHA, OSV_ACTION_TAG)} From d9356742fa2ea104f4adedefc8f3976378cce86c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 04:06:37 +0900 Subject: [PATCH 2/5] fix: align OSV reporter with scanner release --- .github/workflows/osv-scanner-pr.yml | 2 +- .github/workflows/security-scan.yml | 2 +- CHANGELOG.md | 3 ++- .../osv-scanner-action-single-version.md | 19 ++++++++++--------- tests/test_osv_scanner_action_pin_contract.py | 17 +++++++++++++++-- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 00bbf2c81..274ce193e 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -39,7 +39,7 @@ jobs: # full old/new osv-scanner JSON into job outputs unconditionally, tripping # GitHub's 1,048,576-byte job-outputs cap and failing the run. Same nested # action pins as v2.3.8; only the Export step is now conditional. - uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@3a7550f43ba5b58905a821ce3a0ed24c4858b3f4 # v2.3.8 + export-results gate + uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 + export-results gate permissions: actions: read contents: read diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 1554d563d..19e07fcd4 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -189,7 +189,7 @@ jobs: if len(findings) > 50: print(f"... {len(findings) - 50} additional {label} OSV finding(s) omitted from the log summary.") - name: Report PR-introduced OSV findings - uses: google/osv-scanner-action/osv-reporter-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8 + uses: google/osv-scanner-action/osv-reporter-action@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 with: scan-args: | --output=results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c11c39fc..ec4f86e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,8 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Aligned every central OSV-Scanner Action use to the official v2.5.1 commit, +- Aligned every central OSV-Scanner Action scanner, reporter, and reusable + workflow use to the official v2.5.1 commit, preserving exact-base/head policy while applying the current namespace, local-cache, and offline-vulnerability matching fixes. - Publish only the sanitized cumulative Strix report tree, avoiding a later diff --git a/docs/doctoring/osv-scanner-action-single-version.md b/docs/doctoring/osv-scanner-action-single-version.md index d79d7c72e..accbc7636 100644 --- a/docs/doctoring/osv-scanner-action-single-version.md +++ b/docs/doctoring/osv-scanner-action-single-version.md @@ -2,14 +2,14 @@ ## Incident boundary -The central dependency scan invokes `google/osv-scanner-action` four times for -base, head, and reporter evidence. All four uses remained on Action v2.3.8, -while the stale dependency pull request targeted v2.5.0 after v2.5.1 had become -the current official release. +The central dependency scans invoke `google/osv-scanner-action` six times: +four scanner steps, their reporter gate, and the reusable pull-request scan. +All six remained on Action v2.3.8, while the stale dependency pull request +targeted v2.5.0 after v2.5.1 had become the current official release. ## Decision -Pin every central `google/osv-scanner-action/osv-scanner-action` use to +Pin every central scanner, reporter, and reusable workflow use to `6e4298ebc4db23e847df9b2e2de2939d6f066c67`, the commit referenced by the official v2.5.1 tag. That release preserves package namespaces, restores the local database cache environment variable, and fixes offline vulnerability @@ -17,10 +17,11 @@ matching (Google, 2026). GitHub documents that a full commit SHA is unique and immutable and should be verified against the action repository (GitHub, n.d.). A repository-wide -contract therefore parses every central workflow occurrence, rejects malformed -pins or mismatched comments, and admits only the reviewed v2.5.1 SHA and tag. -Scan arguments, timeouts, permissions, exact-base/head comparison, reporter -gates, and fail-closed dependency policy are unchanged. +contract therefore parses every central workflow occurrence, checks the exact +component inventory, rejects malformed pins or mismatched comments, and admits +only the reviewed v2.5.1 SHA and tag. Scan arguments, timeouts, permissions, +exact-base/head comparison, reporter gates, and fail-closed dependency policy +are unchanged. ## References diff --git a/tests/test_osv_scanner_action_pin_contract.py b/tests/test_osv_scanner_action_pin_contract.py index 4b2132bb9..f7ffbdc5f 100644 --- a/tests/test_osv_scanner_action_pin_contract.py +++ b/tests/test_osv_scanner_action_pin_contract.py @@ -3,6 +3,7 @@ from __future__ import annotations import re +from collections import Counter from pathlib import Path @@ -10,23 +11,35 @@ OSV_ACTION_SHA = "6e4298ebc4db23e847df9b2e2de2939d6f066c67" OSV_ACTION_TAG = "v2.5.1" _PIN = re.compile( - r"google/osv-scanner-action/osv-scanner-action@" + r"google/osv-scanner-action/(?P" + r"osv-scanner-action|osv-reporter-action|" + r"\.github/workflows/osv-scanner-reusable-pr\.yml)@" r"(?P[^\s]+)\s+#\s+(?Pv[^\s]+)" ) +_EXPECTED_COMPONENTS = Counter( + { + "osv-scanner-action": 4, + "osv-reporter-action": 1, + ".github/workflows/osv-scanner-reusable-pr.yml": 1, + } +) def test_all_osv_scanner_actions_share_the_reviewed_current_release() -> None: """Reject partial bumps, malformed refs, and stale OSV Action comments.""" observed: set[tuple[str, str]] = set() + components: Counter[str] = Counter() for path in sorted((REPO_ROOT / ".github/workflows").glob("*.yml")): for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1 ): - if "google/osv-scanner-action/osv-scanner-action@" not in line: + if "google/osv-scanner-action/" not in line: continue match = _PIN.search(line) assert match is not None, f"malformed OSV Action pin: {path}:{line_number}" observed.add((match.group("sha"), match.group("tag"))) + components[match.group("component")] += 1 assert observed == {(OSV_ACTION_SHA, OSV_ACTION_TAG)} + assert components == _EXPECTED_COMPONENTS From 808b05ef5aec46f323dcdc7edaffef719d08b483 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 11:11:48 +0900 Subject: [PATCH 3/5] docs(osv): align reusable workflow pin rationale --- .github/workflows/osv-scanner-pr.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 274ce193e..ac7fe7123 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -34,11 +34,11 @@ jobs: osv-scan: if: github.event.action != 'closed' # ponytail: use upstream reusable PR workflow, don't hand-roll the diff scan - # Pinned to v2.3.8 + 1 commit (3a7550f) which gates the JSON job outputs - # behind the new `export-results` input (default false). v2.3.8 dumped the - # full old/new osv-scanner JSON into job outputs unconditionally, tripping - # GitHub's 1,048,576-byte job-outputs cap and failing the run. Same nested - # action pins as v2.3.8; only the Export step is now conditional. + # Pinned to the official v2.5.1 commit, whose reusable workflow exposes + # `export-results` with a false default. Leaving that input false keeps the + # full old/new osv-scanner JSON out of job outputs and avoids GitHub's + # 1,048,576-byte job-output cap; v2.5.1 makes its Export results step + # conditional on that input. uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@6e4298ebc4db23e847df9b2e2de2939d6f066c67 # v2.5.1 + export-results gate permissions: actions: read From 0a3ac2c570f6ff55f8a84b9cb7043ea321a622d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:09:49 +0900 Subject: [PATCH 4/5] test(osv): cover yaml workflow pins --- tests/test_osv_scanner_action_pin_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_osv_scanner_action_pin_contract.py b/tests/test_osv_scanner_action_pin_contract.py index f7ffbdc5f..210bc88f7 100644 --- a/tests/test_osv_scanner_action_pin_contract.py +++ b/tests/test_osv_scanner_action_pin_contract.py @@ -30,7 +30,7 @@ def test_all_osv_scanner_actions_share_the_reviewed_current_release() -> None: observed: set[tuple[str, str]] = set() components: Counter[str] = Counter() - for path in sorted((REPO_ROOT / ".github/workflows").glob("*.yml")): + for path in sorted((REPO_ROOT / ".github/workflows").glob("*.y*ml")): for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1 ): From 5b1408923b9708246888c41f94e66d9ae85f3621 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:10:48 +0900 Subject: [PATCH 5/5] test(osv): match action uses only --- tests/test_osv_scanner_action_pin_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_osv_scanner_action_pin_contract.py b/tests/test_osv_scanner_action_pin_contract.py index 210bc88f7..26bfcf062 100644 --- a/tests/test_osv_scanner_action_pin_contract.py +++ b/tests/test_osv_scanner_action_pin_contract.py @@ -34,7 +34,7 @@ def test_all_osv_scanner_actions_share_the_reviewed_current_release() -> None: for line_number, line in enumerate( path.read_text(encoding="utf-8").splitlines(), start=1 ): - if "google/osv-scanner-action/" not in line: + if "uses:" not in line or "google/osv-scanner-action/" not in line: continue match = _PIN.search(line) assert match is not None, f"malformed OSV Action pin: {path}:{line_number}"