Use when a repo needs dependency CVE scanning (SCA) in CI, a team is choosing between native audits (npm audit, pip-audit, cargo audit), OSV-Scanner and Snyk, or findings need triage. Scans lockfiles with thresholds, checks packages were seen, outputs SARIF and allows only expiring ignores.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Dependency Vulnerability Scanning?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/testland-dependency-vulnerability-scanning)
---
name: dependency-vulnerability-scanning
description: "Use when a repo needs dependency CVE scanning (SCA) in CI, a team is choosing between native audits (npm audit, pip-audit, cargo audit), OSV-Scanner and Snyk, or findings need triage. Scans lockfiles with thresholds, checks packages were seen, outputs SARIF and allows only expiring ignores."
metadata:
keywords: "sca, dependency-scanning, osv-scanner, snyk, npm-audit, pip-audit, cargo-audit, bundler-audit, owasp-dependency-check"
version: "1.0.1"
---
# dependency-vulnerability-scanning
## Overview
Software composition analysis (SCA) matches the dependencies a repo
declares (lockfiles, manifests, SBOMs) against vulnerability advisory
databases. Each scanner family reads a different database, so their
coverage differs:
| Family | Database | Cost |
|---|---|---|
| Native audits (`npm audit`, `pip-audit`, `cargo audit`, `bundle-audit`, Dependency-Check) | The ecosystem's own advisory feed (npm, PyPA, RustSec, RubySec, NVD) | Free; usually already installed |
| OSV-Scanner | OSV.dev, aggregated across ecosystems ([OSV-Scanner usage][osv-usage]) | Free, open source |
| Snyk | Snyk's own database, plus `snyk monitor` alerts ([Snyk CLI][sn-gh]) | Commercial license |
The workflow below is the same for all three; the per-tool detail is
in the references.
## When to use
- A repo has no dependency scanning in CI yet.
- The team must choose between native audits, OSV-Scanner and Snyk, or
layer two of them.
- A scanner reports findings and someone wants to suppress, waive or
gate on them.
## Step 1 - Pick the scanner
| Repo situation | Start with | Reference |
|---|---|---|
| One ecosystem, fastest first gate, nothing extra to install | That ecosystem's native audit | [references/native-audits.md](references/native-audits.md) |
| Several ecosystems, open source only, or an SBOM as input | OSV-Scanner | [references/osv-scanner.md](references/osv-scanner.md) |
| A Snyk license, or alerts on new CVEs against pinned versions | Snyk | [references/snyk.md](references/snyk.md) |
For full coverage run one native audit plus one cross-ecosystem
scanner (OSV-Scanner or Snyk): each database misses advisories the
others carry.
## Step 2 - Scan the committed lockfile
Scan lockfiles, not loose manifests: a manifest range resolves to
whatever is newest today, a lockfile is what actually ships.
```bash
npm audit --audit-level=high # npm; also yarn npm audit / pnpm audit
pip-audit -r requirements.txt # Python
cargo audit # Rust
bundle-audit check --update # Ruby; --update refreshes the advisory DB
osv-scanner scan -L package-lock.json # one lockfile
osv-scanner scan -r ./ # every lockfile and SBOM under ./
snyk test --all-projects --severity-threshold=high
```
SBOM input: OSV-Scanner picks up SBOMs whose names follow the SPDX or
CycloneDX patterns (`*.spdx.json`, `bom.json`, `*.cdx.json`) and scans
one directly with `osv-scanner scan source -L my-app.cdx.json`; the
components need Package URLs ([OSV-Scanner source scanning][osv-src]).
Snyk scans one with `snyk sbom test --file=my-app.cdx.json` (CycloneDX
1.4-1.6 JSON or SPDX 2.3 JSON, Enterprise plans) ([Snyk sbom test][sn-sbom]).
`syft-generation` produces the SBOM.
## Step 3 - Check the scan saw packages
A scan that found no lockfile passes silently. Before trusting a green
result, confirm the scanner reported the packages you expect.
OSV-Scanner makes this explicit with its exit codes
([OSV-Scanner output][osv-out]):
| Exit code | Meaning | CI action |
|---|---|---|
| 0 | Packages found, no known vulnerabilities | Pass |
| 1 | Packages found, vulnerabilities found | Fail (or triage, Step 5) |
| 127 | General error | Fail |
| 128 | No packages found | Fail: the scan did nothing; fix the path or lockfile name |
For the other scanners, read the JSON report's dependency count, or
run once against a branch with a known-vulnerable pin and confirm the
job goes red.
## Step 4 - Emit machine-readable output
```bash
osv-scanner scan -r ./ --format sarif --output-file osv.sarif
snyk test --sarif-file-output=snyk.sarif
pip-audit --format sarif --output pip-audit.sarif
npm audit --json > npm-audit.json
```
`--format` and `--output-file` are OSV-Scanner's output flags
([OSV-Scanner usage][osv-usage]); the container image takes the same
arguments ([installation][osv-install]). Upload SARIF to code scanning, and
pass two or more reports to `multi-tool-finding-triage` (or the
`security-finding-triager` agent) to dedupe findings across scanners.
## Step 5 - False-positive triage
Suppress a finding only in a committed file, per advisory, with a
reason, an approver and an expiry date, so it comes back for review:
| Scanner | Suppression | Expiry |
|---|---|---|
| Snyk | `.snyk` `ignore:` entry | `expires:` (required) |
| OSV-Scanner | `osv-scanner.toml` `[[IgnoredVulns]]` | `ignoreUntil` |
| pip-audit | `--ignore-vuln <id>` | Record in a committed ignore list |
| Dependency-Check | `dependency-check-suppressions.xml` | `<suppress until="2026-12-31Z">` ([suppression docs][dc-supp]) |
| cargo audit | `.cargo/audit.toml` `[advisories] ignore` | Record in the justification |
| bundler-audit | `.bundler-audit.yml` `ignore:` | Record in the justification |
Before suppressing as "not exploitable", prove it: `reachability-analyzer`
checks whether the vulnerable function is called, and
`cve-exploitability-triage` weighs EPSS and KEV. A severity threshold
(`--audit-level`, `--severity-threshold`) is a scan filter, not a
suppression. Templates per tool: [references/snyk-policy-and-triage.md](references/snyk-policy-and-triage.md),
[references/osv-scanner-config-and-ci.md](references/osv-scanner-config-and-ci.md),
[references/ecosystem-config-and-triage.md](references/ecosystem-config-and-triage.md),
[references/cargo-bundle-audit.md](references/cargo-bundle-audit.md).
## Step 6 - Gate CI
```yaml
jobs:
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- if: hashFiles('package-lock.json') != ''
run: npm audit --audit-level=high
- if: hashFiles('requirements.txt') != ''
run: pipx run pip-audit -r requirements.txt
- name: OSV-Scanner (fails on findings and on exit 128)
run: |
docker run --rm -v "$PWD:/src" ghcr.io/google/osv-scanner:latest \
scan -r /src --format sarif --output-file /src/osv.sarif
- if: always()
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: osv.sarif }
```
With a Snyk license, add `snyk test --severity-threshold=high` on pull
requests and `snyk monitor` on the default branch, which snapshots the
dependency tree and alerts on CVEs disclosed later
([Snyk CLI][sn-gh]); the full job is in [references/snyk.md](references/snyk.md).
## Worked example
A Node + Python service has no SCA. Step 1: two ecosystems, no Snyk
license, so `npm audit` and `pip-audit` as the fast gate plus
OSV-Scanner for cross-ecosystem coverage. The first OSV run exits 128:
the Python lockfile is `requirements-lock.txt`, a name OSV-Scanner does
not detect, so the job is pointed at it with `-L requirements-lock.txt`
and now exits 1 with two findings. One is a `lodash` advisory in a
function the service never calls; `reachability-analyzer` confirms it,
and an `[[IgnoredVulns]]` entry with `ignoreUntil` 90 days out and the
approver's name goes into `osv-scanner.toml`. The other is upgraded.
The job goes green, and the ignore resurfaces when it expires.
## Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| No severity threshold on the first run | Low-severity noise; the team turns the job off | `--audit-level=high` / `--severity-threshold=high` |
| Treating any non-1 exit as green | OSV exit 128 means nothing was scanned | Fail on 128 (Step 3) |
| Suppression with no expiry | Debt never comes back for review | `expires:` / `ignoreUntil` (Step 5) |
| One scanner only | Each advisory database has gaps | Native audit plus OSV-Scanner or Snyk (Step 1) |
| `npm audit fix --force` in CI | Silently bumps major versions | Review forced upgrades by hand |
| Token in scripts | Leaks `SNYK_TOKEN` | CI secret |
## Limitations
- No scanner here does reachability: every advisory on a declared
dependency counts, used or not. Use `reachability-analyzer`.
- Coverage differs by ecosystem in every database.
- Container images need `trivy-image` or the Grype flow in
`syft-generation`.
- Automated upgrade PRs are `dependabot-config` and `renovate-config`.
## References
- [OSV-Scanner usage][osv-usage] - `-L`, `-r`, `--format`, `--output-file`, `--config`.
- [OSV-Scanner source scanning][osv-src] - SBOM file-name patterns, `scan source -L`.
- [OSV-Scanner output][osv-out] - output formats and exit codes.
- [OSV-Scanner installation][osv-install] - `ghcr.io/google/osv-scanner` image.
- [Dependency-Check suppression][dc-supp] - suppression file, `until` expiry.
- [Snyk CLI][sn-gh] - `snyk test`, `snyk monitor`, subcommands.
- [Snyk sbom test][sn-sbom] - `snyk sbom test --file`, supported formats, Enterprise plans.
- Per-tool references: [references/native-audits.md](references/native-audits.md),
[references/osv-scanner.md](references/osv-scanner.md),
[references/snyk.md](references/snyk.md).
- Sibling skills: `syft-generation` (SBOMs), `reachability-analyzer`,
`cve-exploitability-triage`, `multi-tool-finding-triage`,
`dependabot-config`, `renovate-config`, `trivy-image`.
[osv-usage]: https://google.github.io/osv-scanner/usage/
[osv-src]: https://github.com/google/osv-scanner/blob/main/docs/scan-source.md
[osv-out]: https://google.github.io/osv-scanner/output/
[osv-install]: https://github.com/google/osv-scanner/blob/main/docs/installation.md
[dc-supp]: https://jeremylong.github.io/DependencyCheck/general/suppression.html
[sn-gh]: https://github.com/snyk/snyk
[sn-sbom]: https://docs.snyk.io/developer-tools/snyk-cli/commands/sbom-test