Add OSV-Scanner-based security workflow#388
Conversation
Single workflow, single job, three triggers:
- pull_request to main: fails on CVSS >= 7 findings only
(HIGH/CRITICAL block merges; MED/LOW visible but non-blocking)
- cron weekly (Sunday 00:00 UTC): reports ALL findings via email
- workflow_dispatch: behaves like cron
Mirrors the JDBC driver's security workflow (databricks-jdbc#1460)
adapted for Node.js:
- Reads package-lock.json natively via OSV-Scanner --lockfile (no
separate SBOM tool needed)
- Reuses the existing ./.github/actions/setup-jfrog composite action
for parity with main.yml (the workflow functionally doesn't need
JFrog since OSV reads the lockfile directly, but keeping the
composite action preserves the established pattern)
- Suppressions in osv-scanner.toml ([[IgnoredVulns]] schema)
The workflow is not yet wired into branch protection. Day-one scan
against current main surfaces 22 HIGH / 15 MED / 5 LOW (42 total).
Many are in dev dependencies (mocha/nyc/eslint chains). The team can
either bump the offending deps or add documented [[IgnoredVulns]]
entries for dev-only findings that don't reach `dist/`.
Co-authored-by: Isaac
Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
|
▎ .max_severity // "0" only substitutes when max_severity is null/false, NOT when it is an empty string. OSV-Scanner emits max_severity as "" for advisories lacking a CVSS vector (common for GHSA-only advisories). Such a finding gets severity="" -> tonumber? // 0 -> 0, so a genuinely HIGH vuln never trips the CVSS>=7 PR gate (fail-open). Validator: confirm OSV v2.3.8 emits max_severity="" for scoreless advisories and that the JDBC bouncycastle-style GHSA findings carry no numeric max_severity. ▎ Collect findings runs set -uo pipefail (no -e). If /tmp/osv-out.json is malformed JSON, the ALL_FINDINGS jq exits 5, ALL_FINDINGS='', then TOTAL_FINDINGS='' and HIGH_COUNT='' are written to GITHUB_OUTPUT as empty strings. Confirmed locally: [ "$TOTAL_FINDINGS" -gt 0 ] on line 142 errors with 'integer expression expected'. Downstream high_count != '0' becomes '' != '0' (true). Validator: trace empty-output propagation to the gate steps. 📄 .github/workflows/securityScan.yml
▎ || true swallows every osv-scanner failure, and the only backstop ([ ! -s ]) misses a partial write. Line 89's || true was intended to tolerate osv-scanner's exit-1-on-any-finding, but it also masks genuine failures: a network error reaching OSV.dev, a malformed osv-scanner.toml, a wrong-arch/corrupt binary, or a crash mid-write to --output-file. The sole thing distinguishing those from a clean scan is the line-91 [ ! -s /tmp/osv-out.json ] guard, which only rejects a zero-byte file. A truncated-but-non-empty output file (reproduced: a 140-byte partial JSON) passes the guard, then the Collect-findings jq (line 110) fails to parse it and empty counts propagate to the gates -- an errored scan becomes indistinguishable from a clean one (fail-open on the scheduled path) or emits misleading counts. (Note: the sibling concern that scan source --lockfile --config --format=json --output-file is invalid v2.3.8 CLI is not a bug -- that syntax is valid and was verified against the v2.3.8 binary.) |
Summary
.github/workflows/securityScan.yml— single workflow, single job, three triggers (PR / weekly cron / manual). PR runs fail on CVSS ≥ 7 only; weekly runs report all findings and email the team.osv-scanner.toml— empty suppressions file (populate iteratively as real false positives or dev-only findings surface)../.github/actions/setup-jfrogcomposite action — no duplicate OIDC-token logic.Mirrors the JDBC driver's workflow (databricks-jdbc#1460), adapted for Node.js: reads
package-lock.jsonnatively via OSV-Scanner (no separate SBOM tool needed).Day-one results
The workflow is not yet wired into branch protection, so its first PR-time runs are advisory. A dry-run against current
mainsurfaces:form-data,basic-ftp,flatted,minimatch,thrift,ws,serialize-javascript,cross-spawn,path-to-regexp,braces,picomatch,@75lb/deep-mergeImportant: OSV scans both runtime and devDependencies (it treats everything in
package-lock.jsonequally). Many of the day-one findings are dev-only (the mocha/nyc/eslint toolchain —flatted,serialize-javascript,nanoid,js-yaml, etc.) and don't reachdist/. The team has two options for those:npm updateaway).[[IgnoredVulns]]entry toosv-scanner.tomljustifying why the CVE is dev-only and doesn't affect shipped artifacts.A follow-up PR can either bump deps or curate the suppression list once we triage what's runtime vs. dev.
Test plan
package-lock.json— produces expected findingsworkflow_dispatchafter merge exercises the weekly pathSMTP_USERNAME,SMTP_PASSWORD,EMAIL_RECIPIENTS) wired in repo settings before the first scheduled runThis pull request was AI-assisted by Isaac.