@@ -95,23 +95,51 @@ jobs:
9595 mkdir -p "$OSV_SCANNER_LOCAL_DB_CACHE_DIRECTORY"
9696 # outputName uses ${project.version}; resolve the actual emitted file.
9797 SBOM=$(ls target/*-bom.json | head -n1)
98- echo "Scanning SBOM: $SBOM"
98+ if [ -z "${SBOM:-}" ] || [ ! -f "$SBOM" ]; then
99+ echo "::error::CycloneDX SBOM not found under target/*-bom.json — cannot run the CVE gate."
100+ exit 1
101+ fi
102+ # osv-scanner detects SBOM format from the FILENAME; '<name>-bom.json'
103+ # is not a recognized CycloneDX name (it silently parses nothing), so
104+ # copy to a recognized '.cdx.json' name before scanning.
105+ SBOM_CDX="$RUNNER_TEMP/bom.cdx.json"
106+ cp "$SBOM" "$SBOM_CDX"
107+ echo "Scanning SBOM: $SBOM (as $SBOM_CDX)"
99108 # --offline-vulnerabilities: never contacts the live feed during scan.
100109 # --download-offline-databases: refresh the cached DB if cache missed.
101- # osv-scanner exits 1 on ANY finding; we ignore its exit code and gate
102- # ourselves on CVSS severity from the JSON instead.
110+ # -L: lockfile/SBOM input (replaces the deprecated --sbom).
111+ set +e
103112 osv-scanner scan \
104113 --offline-vulnerabilities \
105114 --download-offline-databases \
106115 --format json \
107- --sbom "$SBOM" > osv-results.json || true
116+ -L "$SBOM_CDX" > osv-results.json 2> osv-stderr.txt
117+ OSV_EXIT=$?
118+ set -e
119+ cat osv-stderr.txt || true
120+ # FAIL CLOSED: osv-scanner exits 0 (no vulns) or 1 (vulns found) on a
121+ # real scan; any other exit code, a parse error, or a missing results
122+ # object means NOTHING was scanned — the gate must fail, never pass on
123+ # a non-scan (the bug this replaces let a non-scan pass green).
124+ if [ "$OSV_EXIT" != "0" ] && [ "$OSV_EXIT" != "1" ]; then
125+ echo "::error::osv-scanner did not complete a scan (exit $OSV_EXIT) — failing closed."
126+ exit 1
127+ fi
128+ if grep -qiE "Failed to parse|invalid SBOM" osv-stderr.txt; then
129+ echo "::error::osv-scanner could not ingest the SBOM — failing closed."
130+ exit 1
131+ fi
132+ if ! jq -e 'has("results")' osv-results.json >/dev/null 2>&1; then
133+ echo "::error::osv-scanner produced no results object — failing closed."
134+ exit 1
135+ fi
108136 # Gate: max_severity is a CVSS base-score string per vulnerability
109137 # group. >= 7.0 == HIGH or CRITICAL (CVSS v3). Anything below is
110138 # non-blocking per security.md. An unscored group (null or "") is
111139 # coerced to 0 so a missing CVSS never crashes or trips the gate.
112140 SEV='((.max_severity // "0") | if . == "" then "0" else . end | tonumber)'
113141 HIGH=$(jq "[.results[]?.packages[]?.groups[]? | ${SEV} | select(. >= 7.0)] | length" osv-results.json)
114- echo "HIGH/CRITICAL findings (CVSS >= 7.0): ${HIGH}"
142+ echo "HIGH/CRITICAL findings (CVSS >= 7.0): ${HIGH:-0 }"
115143 jq -r ".results[]?.packages[]? | .package as \$p | .groups[]? | ${SEV} as \$s | select(\$s >= 7.0) | \" BLOCK \(\$p.name)@\(\$p.version) CVSS=\(.max_severity) \(.ids | join(\",\"))\"" osv-results.json || true
116144 if [ "${HIGH:-0}" -gt 0 ]; then
117145 echo "::error::${HIGH} HIGH/CRITICAL dependency vulnerabilit(ies) found — failing per security policy."
0 commit comments