Skip to content

exit_code_redirect.sh ignores --allow-no-lockfiles=false in multiline Action input #3005

Description

@darpadebt

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions