Skip to content
12 changes: 6 additions & 6 deletions .github/workflows/osv-scanner-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ 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.
uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@3a7550f43ba5b58905a821ce3a0ed24c4858b3f4 # v2.3.8 + export-results gate
# 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
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
permissions:
actions: read
contents: read
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Comment thread
seonghobae marked this conversation as resolved.
with:
scan-args: |
--format=json
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- 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
copy of relative scanner output that could reintroduce known internal warning
text into uploaded security evidence.
Expand Down
33 changes: 33 additions & 0 deletions docs/doctoring/osv-scanner-action-single-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# OSV-Scanner Action single-version boundary

## Incident boundary

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 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
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, 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

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
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Google. (2026, August 17). *OSV-Scanner Action v2.5.1* [Software release].
https://github.com/google/osv-scanner-action/releases/tag/v2.5.1
45 changes: 45 additions & 0 deletions tests/test_osv_scanner_action_pin_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Keep every central OSV-Scanner Action use on one reviewed release."""

from __future__ import annotations

import re
from collections import Counter
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/(?P<component>"
r"osv-scanner-action|osv-reporter-action|"
r"\.github/workflows/osv-scanner-reusable-pr\.yml)@"
r"(?P<sha>[^\s]+)\s+#\s+(?P<tag>v[^\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("*.y*ml")):
for line_number, line in enumerate(
path.read_text(encoding="utf-8").splitlines(), start=1
):
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}"
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
Loading