The problem
Update-time pins a GitHub Action to a commit SHA because an action is a supply-chain risk, and then never asks whether the version it pinned is one a security advisory names. The README explains the silence under the per-type "Vulnerable dependencies" answer. OSV holds advisories for actions, it says, but "their affected entries enumerate no versions", so a query "comes back empty however that version is spelled", and "silence there says nothing about whether the action is safe".
The conclusion is right and the reason is wrong, and the difference is what makes a check possible. OSV holds advisories for actions, and those advisories do carry versions (probed 2026-08-15 against https://api.osv.dev/v1/query with ecosystem GitHub Actions):
| Action |
Advisory |
What the advisory says is affected |
tj-actions/changed-files |
GHSA-mrrh-fwg8-r2c3 |
range, introduced: 0 → fixed: 46.0.1 |
tj-actions/changed-files |
GHSA-mcph-m25j-8j63 |
range, introduced: 0 → fixed: 41 |
reviewdog/action-setup |
GHSA-qmg3-hpqr-gqvc |
versions: ['1'] |
What OSV declines to do is the matching. The same query carrying a version comes back empty, whatever the version is spelled like:
version '45.0.7': 0 advisories
version 'v45.0.7': 0 advisories
version '45': 0 advisories
So the advisory covering 45.0.7 sits in OSV's answer to a version-less query and is missing from its answer to a version-bearing one. Update-time asks the version-bearing question, receives nothing, and reports nothing. GHSA-mrrh-fwg8-r2c3 is the March 2025 tj-actions/changed-files compromise, in which the tags of released versions were repointed at a commit that wrote CI secrets into build logs — the attack that hash pinning exists to blunt, and the one whose advisory Update-time cannot currently see.
What the check would do
Ask OSV for the action's advisories without naming a version, then match the version the run leaves the reference on against each advisory's ranges and versions entries. Report a match exactly as a vulnerable Python dependency is reported today: at the reference's line, at the advisory's risk level, naming the advisory and its URL.
Scope
- The references that name a version: an action referenced by a version tag (
uses: actions/checkout@v4, @4.1.1) and one already pinned to a commit SHA carrying a version comment (@<sha> # v4.1.1). An action pinned to a bare SHA or to a branch names no version, so it stays unchecked.
- The
vulnerable scope of the # update-time: marker, which Update-time currently reports as redundant on an action, has to start holding the new warning back — in all its forms, so ignore[vulnerable], ignore[vulnerable=ID], and ignore[vulnerable<LEVEL].
--vulnerability-level and --ignore-vulnerability apply as they do for every other checked reference.
The design constraint
The version matching becomes Update-time's, since OSV will not do it. An advisory's ranges are ECOSYSTEM-typed, which means they are ordered by the ecosystem's own comparison, and an action's versions are git tags: sometimes v-prefixed, sometimes major-only, and fixed: 41 above is a major-only bound. Deciding that 45.0.7 falls below 46.0.1, and what a v4 pin means against a fixed: 41, needs a comparison that tolerates all of those shapes. Update-time already orders such tags when it picks the latest version to move an action to, so the comparison itself is not new; what is new is applying it to a bound the advisory states rather than to a tag the repository publishes.
Out of scope
- Pre-commit hooks. A hook repository is not a package in OSV's GitHub Actions ecosystem unless that same repository also publishes an action, so the
vulnerable scope stays redundant there.
- Reading an advisory's remediation to force an update past the fix. The update path already moves a reference to the newest version its bounds allow.
Documentation
- The per-type "Vulnerable dependencies" answer for GitHub Actions and pre-commit hooks, which currently says neither is checked and why.
- The Python-dependency-style row for GitHub Actions in the dependency-type table under "Vulnerable dependencies".
- The "Redundant markers" section, which names a
vulnerable scope on a GitHub Action as reported redundant.
- A changelog entry under
[Unreleased].
Open questions
- Does Update-time match
ECOSYSTEM ranges itself for actions alone, or does that matching move into the OSV source for every ecosystem, so a future ecosystem OSV cannot match is handled the same way?
- What is reported for a reference whose version names a line rather than a release — a
@v4 tag, or a # v4 comment — when the advisory's bound is fixed: 4.1.2? Every 4.x below the fix is affected and every one above it is not, and the reference names neither.
- Do reusable workflows (
uses: org/repo/.github/workflows/ci.yml@v1) get the same check, given that OSV would know them under the repository's name?
The problem
Update-time pins a GitHub Action to a commit SHA because an action is a supply-chain risk, and then never asks whether the version it pinned is one a security advisory names. The README explains the silence under the per-type "Vulnerable dependencies" answer. OSV holds advisories for actions, it says, but "their affected entries enumerate no versions", so a query "comes back empty however that version is spelled", and "silence there says nothing about whether the action is safe".
The conclusion is right and the reason is wrong, and the difference is what makes a check possible. OSV holds advisories for actions, and those advisories do carry versions (probed 2026-08-15 against
https://api.osv.dev/v1/querywith ecosystemGitHub Actions):tj-actions/changed-filesGHSA-mrrh-fwg8-r2c3introduced: 0→fixed: 46.0.1tj-actions/changed-filesGHSA-mcph-m25j-8j63introduced: 0→fixed: 41reviewdog/action-setupGHSA-qmg3-hpqr-gqvc['1']What OSV declines to do is the matching. The same query carrying a version comes back empty, whatever the version is spelled like:
So the advisory covering
45.0.7sits in OSV's answer to a version-less query and is missing from its answer to a version-bearing one. Update-time asks the version-bearing question, receives nothing, and reports nothing.GHSA-mrrh-fwg8-r2c3is the March 2025tj-actions/changed-filescompromise, in which the tags of released versions were repointed at a commit that wrote CI secrets into build logs — the attack that hash pinning exists to blunt, and the one whose advisory Update-time cannot currently see.What the check would do
Ask OSV for the action's advisories without naming a version, then match the version the run leaves the reference on against each advisory's
rangesandversionsentries. Report a match exactly as a vulnerable Python dependency is reported today: at the reference's line, at the advisory's risk level, naming the advisory and its URL.Scope
uses: actions/checkout@v4,@4.1.1) and one already pinned to a commit SHA carrying a version comment (@<sha> # v4.1.1). An action pinned to a bare SHA or to a branch names no version, so it stays unchecked.vulnerablescope of the# update-time:marker, which Update-time currently reports as redundant on an action, has to start holding the new warning back — in all its forms, soignore[vulnerable],ignore[vulnerable=ID], andignore[vulnerable<LEVEL].--vulnerability-leveland--ignore-vulnerabilityapply as they do for every other checked reference.The design constraint
The version matching becomes Update-time's, since OSV will not do it. An advisory's ranges are
ECOSYSTEM-typed, which means they are ordered by the ecosystem's own comparison, and an action's versions are git tags: sometimesv-prefixed, sometimes major-only, andfixed: 41above is a major-only bound. Deciding that45.0.7falls below46.0.1, and what av4pin means against afixed: 41, needs a comparison that tolerates all of those shapes. Update-time already orders such tags when it picks the latest version to move an action to, so the comparison itself is not new; what is new is applying it to a bound the advisory states rather than to a tag the repository publishes.Out of scope
vulnerablescope stays redundant there.Documentation
vulnerablescope on a GitHub Action as reported redundant.[Unreleased].Open questions
ECOSYSTEMranges itself for actions alone, or does that matching move into the OSV source for every ecosystem, so a future ecosystem OSV cannot match is handled the same way?@v4tag, or a# v4comment — when the advisory's bound isfixed: 4.1.2? Every4.xbelow the fix is affected and every one above it is not, and the reference names neither.uses: org/repo/.github/workflows/ci.yml@v1) get the same check, given that OSV would know them under the repository's name?