Summary
The Docker Action passes multiline scan-args as its final argument.
exit_code_redirect.sh splits and executes those values from split_args,
but when osv-scanner exits 128, the wrapper checks only the separate
prefix-argument collection for --allow-no-lockfiles=false.
In standard Action invocation, the prefix collection is empty. The explicit
fail-closed flag is executed but not inspected, and exit 128 is converted
to 0.
Current affected source
Reproduced against:
Repository: google/osv-scanner
Commit: c84fa4568f2526d0333e9a914ea8a0a5f74ad68b
Release: v2.5.1
File: exit_code_redirect.sh
Git blob: 2e7482fa65b8b45cf5c0687452d58646c28e0990
File SHA-256: 4b6d24e5727c636a8a897545f2496223c9ed4426e2692dd096b0b4d08fd1725c
Minimal local reproduction
The following reproduction is for a Linux Bash environment. It creates only a
temporary controlled scanner stub and removes it automatically.
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
cat >"$tmp/osv-scanner" <<'STUB'
#!/usr/bin/env bash
exit 128
STUB
chmod +x "$tmp/osv-scanner"
scan_args=$'--allow-no-lockfiles=false\n--format=json\n--output=results.json\n-r\n./'
set +e
PATH="$tmp:$PATH" bash ./exit_code_redirect.sh "$scan_args"
wrapper_exit=$?
set -e
printf 'wrapper_exit=%s\n' "$wrapper_exit"
Observed:
Exit code: 128
deprecation warning: ...
wrapper_exit=0
Expected:
Exit code: 128
wrapper_exit=128
Root cause
The wrapper executes both the prefix arguments and the split multiline Action
input:
osv-scanner $args "${split_args[@]}"
However, the exit-128 policy loop examines only the prefix argument
collection:
for value in "${args[@]}"; do
The explicit --allow-no-lockfiles=false value is normally present in
split_args. It is therefore passed to and executed by osv-scanner, but it is
omitted from the wrapper's policy decision.
Security-control effect
The validated composition is:
--allow-no-lockfiles=false executed
scanner exit=128
wrapper exit=0
results.json absent
reporter exit=0
empty SARIF produced
modeled required-check outcome=success
This can cause an incomplete dependency scan to be represented as a successful
security check instead of a failed scan.
Proposed remediation
Inspect the same combined argument set that is passed to osv-scanner:
- for value in "${args[@]}"; do
+ # Inspect both prefix arguments and the split multiline Action input.
+ for value in "${args[@]}" "${split_args[@]}"; do
This preserves the existing default and explicit-true behavior while honoring
the explicit false setting.
An in-repository regression test is prepared. It executes the real wrapper with
a controlled scanner stub and covers these paths:
scanner exit 0 -> wrapper 0
scanner exit 1 -> wrapper 1
scanner exit 2 -> wrapper 2
default scanner exit 128 -> wrapper 0 with warning
--allow-no-lockfiles -> wrapper 0
--allow-no-lockfiles=true -> wrapper 0
-allow-no-lockfiles=true -> wrapper 0
--allow-no-lockfiles=false -> wrapper 128
-allow-no-lockfiles=false -> wrapper 128
prefix-form --allow-no-lockfiles=false -> wrapper 128
Validation completed
Baseline negative control:
PASS — the unmodified wrapper failed the new regression as expected
Independent patch applications:
2/2 PASS
patched trees byte-identical
Bash syntax:
2/2 PASS
Targeted Go regression:
2/2 PASS
Go race detector:
2/2 PASS
go vet:
2/2 PASS
gofmt:
2/2 PASS
Behavior matrix:
10/10 PASS on each independent run
Explicit-false stress:
1000/1000 expected results on each independently patched tree
0 unexpected results
Fresh same-input reproduction:
baseline wrapper exit=0
patched wrapper exit=128
executed scanner argument vector byte-identical
The full upstream repository test suite and hosted GitHub Actions end-to-end
workflow have not yet been run and are not claimed. Those will be completed
through the normal pull-request and CI process.
Relationship to prior public work
This issue does not claim novelty for the broader condition where an incomplete
OSV-Scanner workflow can resolve successfully. That family has previously been
discussed in:
google/osv-scanner-action#71
google/osv-scanner-action#139
The narrower residual root described here is that the explicit
--allow-no-lockfiles=false option is executed from split_args but omitted
from the wrapper's exit-128 policy inspection.
Preserving the failure also permits failure-conditioned missing-result checks to
execute instead of being bypassed by a prior 128 -> 0 conversion.
Disclosure and remediation request
This behavior was reported to Google Bug Hunters as case 544980692. Google
closed the report as below its security escalation threshold and explicitly
permitted public disclosure.
The bounded remediation patch and in-repository regression test are prepared.
Per CONTRIBUTING.md, please assign this issue or confirm the preferred test
location and pull-request path so I can submit the remediation through the
repository's normal review process.
No production target, third-party data, hosted workflow, or external repository
was modified during validation.
Summary
The Docker Action passes multiline
scan-argsas its final argument.exit_code_redirect.shsplits and executes those values fromsplit_args,but when
osv-scannerexits128, the wrapper checks only the separateprefix-argument collection for
--allow-no-lockfiles=false.In standard Action invocation, the prefix collection is empty. The explicit
fail-closed flag is executed but not inspected, and exit
128is convertedto
0.Current affected source
Reproduced against:
Minimal local reproduction
The following reproduction is for a Linux Bash environment. It creates only a
temporary controlled scanner stub and removes it automatically.
Observed:
Expected:
Root cause
The wrapper executes both the prefix arguments and the split multiline Action
input:
However, the exit-
128policy loop examines only the prefix argumentcollection:
The explicit
--allow-no-lockfiles=falsevalue is normally present insplit_args. It is therefore passed to and executed byosv-scanner, but it isomitted from the wrapper's policy decision.
Security-control effect
The validated composition is:
This can cause an incomplete dependency scan to be represented as a successful
security check instead of a failed scan.
Proposed remediation
Inspect the same combined argument set that is passed to
osv-scanner:This preserves the existing default and explicit-true behavior while honoring
the explicit false setting.
An in-repository regression test is prepared. It executes the real wrapper with
a controlled scanner stub and covers these paths:
Validation completed
The full upstream repository test suite and hosted GitHub Actions end-to-end
workflow have not yet been run and are not claimed. Those will be completed
through the normal pull-request and CI process.
Relationship to prior public work
This issue does not claim novelty for the broader condition where an incomplete
OSV-Scanner workflow can resolve successfully. That family has previously been
discussed in:
google/osv-scanner-action#71google/osv-scanner-action#139The narrower residual root described here is that the explicit
--allow-no-lockfiles=falseoption is executed fromsplit_argsbut omittedfrom the wrapper's exit-
128policy inspection.Preserving the failure also permits failure-conditioned missing-result checks to
execute instead of being bypassed by a prior
128 -> 0conversion.Disclosure and remediation request
This behavior was reported to Google Bug Hunters as case
544980692. Googleclosed the report as below its security escalation threshold and explicitly
permitted public disclosure.
The bounded remediation patch and in-repository regression test are prepared.
Per
CONTRIBUTING.md, please assign this issue or confirm the preferred testlocation and pull-request path so I can submit the remediation through the
repository's normal review process.
No production target, third-party data, hosted workflow, or external repository
was modified during validation.