Overview
Most package managers ship native audit subcommands that query an ecosystem-specific advisory feed (npm advisories, PyPA, RubySec, Cargo advisory DB, etc.) - the fastest first-line defense: already installed, no extra tooling, runs in seconds. For full coverage, run one native audit + a unified scanner (snyk-test / osv-scanner). The speed/coverage tradeoff table is in references/ecosystem-config-and-triage.md.
When to use
Step 1 - npm / Yarn / pnpm
# npm (built-in since npm 6)
npm audit
npm audit --audit-level=high # filter to HIGH+CRITICAL
npm audit --json > audit.json
npm audit fix # auto-upgrade where compatible
npm audit fix --force # may break: bumps majors
# Yarn 2+ (Berry)
yarn npm audit
yarn npm audit --severity=high
yarn npm audit --recursive # scan all workspaces
# pnpm
pnpm audit
pnpm audit --audit-level high
pnpm audit --jsonSource: docs.npmjs.com/cli/v10/commands/npm-audit + yarnpkg.com/cli/npm/audit + pnpm.io/cli/audit.
Step 2 - pip-audit (Python)
pip install pip-audit
# Scan installed packages in current env
pip-audit
# Scan a requirements file
pip-audit -r requirements.txt
# Scan with PyPA + OSV.dev
pip-audit -s pypi -s osv
# JSON / SARIF output
pip-audit --format json --output pip-audit.json
pip-audit --format sarif --output pip-audit.sarif
# Fix vulnerabilities (auto-upgrade)
pip-audit --fix
# Skip specific CVEs
pip-audit --ignore-vuln GHSA-xxxx-yyyy-zzzzSource: pypi.org/project/pip-audit + github.com/pypa/pip-audit. pip-audit is the official PyPA tool (preferred over the older safety package).
Step 3 - Maven (OWASP Dependency-Check)
Maven has no native mvn audit; use the OWASP Dependency-Check plugin, which fails the build above a CVSS threshold and emits HTML/JSON/SARIF:
mvn dependency-check:checkThe pom.xml plugin block (with failBuildOnCVSS + suppression file) and the Gradle equivalent are in references/ecosystem-config-and-triage.md.
Source: jeremylong.github.io/DependencyCheck/dependency-check-maven/.
Step 4 - cargo audit (Rust)
cargo install cargo-audit
cargo audit
cargo audit --json
cargo audit --deny warnings # treat warnings as errors
cargo audit --ignore RUSTSEC-2023-0001 # specific advisorySource: rustsec.org + github.com/rustsec/rustsec.
Exit codes, per-category --deny flags, the committed .cargo/audit.toml suppression schema, SARIF output, cargo audit fix, binary auditing (cargo audit bin), and the rustsec/audit-check Action are in references/cargo-bundle-audit.md.
Step 5 - bundler-audit (Ruby)
gem install bundler-audit
bundle-audit check # one-time scan
bundle-audit update # refresh advisory DB
bundle-audit check --update # combined refresh + scan
bundle-audit check --ignore CVE-2024-1234 # specific CVESource: github.com/rubysec/bundler-audit.
The committed .bundler-audit.yml waiver template (inline justification + re-review date), Rake task wiring, JSON output, and the CI gate are in references/cargo-bundle-audit.md.
Step 6 - False-positive triage (MANDATORY)
Each native audit has its own suppression mechanism:
| Tool | Suppression |
|---|---|
npm audit | npm audit --omit dev (skip devDependencies) + package.json overrides field for forced version pin |
pip-audit | --ignore-vuln <id> CLI flag (per-CVE) |
dependency-check-maven | dependency-check-suppressions.xml (XML schema with vuln-name regex + reason) |
cargo audit | committed .cargo/audit.toml [advisories] ignore (preferred over --ignore CLI flags - see references/cargo-bundle-audit.md) |
bundle-audit | committed .bundler-audit.yml ignore: list (preferred over --ignore CLI flags - see references/cargo-bundle-audit.md) |
Every suppression carries a mandatory justification (reason + approver + re-review date). The Maven XML template, the AUDIT_IGNORES.md pattern for ad-hoc CLI ignores, and the quarterly review cadence are in references/ecosystem-config-and-triage.md.
Step 7 - CI integration patterns
# Fast first-line gate
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# npm
- if: hashFiles('package-lock.json') != ''
run: npm audit --audit-level=high
# Python
- if: hashFiles('requirements.txt') != ''
run: pip-audit -r requirements.txt
# Maven
- if: hashFiles('pom.xml') != ''
run: mvn dependency-check:check
# Rust
- if: hashFiles('Cargo.lock') != ''
run: cargo audit
# Ruby
- if: hashFiles('Gemfile.lock') != ''
run: bundle-audit check --updateThe if: hashFiles(...) pattern auto-skips ecosystems not present in the repo.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
npm audit without --audit-level | Low-severity noise overwhelms; team disables | Start with --audit-level=high (Step 1) |
npm audit fix --force in CI | Bumps majors silently; breaks builds | Manual review for force-fix; never in CI |
Suppress without Re-review-date | Permanent debt | Mandatory template (Step 6) |
Skip --update for bundle-audit | Stale advisory DB; misses recent CVEs | Always --update (Step 5) |
Ignore bundle-audit check exit code | Findings invisible | Let exit code propagate to CI |
| Use only native audit; skip Snyk/OSV | Per-ecosystem-DB blind spots | Layered (Step 1 cross-ref) |
Limitations
References
Companion reference for npm-pip-maven-audit Steps 4-5. SKILL.md keeps the one-command scans; consult this file for committed suppression files, exit-code semantics, SARIF output, binary auditing, and Rake / GitHub Actions wiring.
cargo audit (Rust)
cargo-audit scans Cargo.lock against the RustSec Advisory Database (rustsec.org/advisories) for vulnerable, unmaintained, unsound, and yanked crates. Minimum Rust version: 1.74 per rustsec-readme (opens in new window).
cargo install cargo-audit --locked
# enable the `cargo audit fix` subcommand:
cargo install cargo-audit --features=fix --locked
cargo audit # run at the workspace root (Cargo.lock)
cargo audit -f path/to/Cargo.lock # explicit lockfile path
cargo audit --no-fetch # skip advisory-db fetch (air-gapped)First run clones the advisory DB into ~/.cargo/advisory-db (rustsec-readme (opens in new window)).
Exit codes and --deny flags
| Code | Meaning |
|---|---|
| 0 | No vulnerabilities / denial criteria not triggered |
| 1 | Vulnerabilities found matching denial criteria |
| 2 | Execution error (missing lockfile, DB fetch failure) |
--deny turns advisory categories into hard failures; --deny warnings is the catch-all that enables all denial categories (cargo-audit audit.rs (opens in new window)):
cargo audit --deny warnings # fail on any vulnerability
cargo audit --deny unmaintained --deny unsound # per-category hard fail
cargo audit --deny yankedOutput formats
--format supports terminal (default), json, and sarif (rustsec-readme (opens in new window)):
cargo audit --format json > cargo-audit.json
cargo audit --format sarif > cargo-audit.sarif # GitHub Code Scanning upload.cargo/audit.toml suppression
Persistent suppression belongs in a committed .cargo/audit.toml, not CLI --ignore flags (not auditable in git). Per the audit.toml example (opens in new window):
# .cargo/audit.toml
[advisories]
ignore = ["RUSTSEC-2024-0999"]
# RUSTSEC-2024-0999: serde_cbor unmaintained; test fixtures only.
# Approved-by: alice@example.com Re-review-date: 2026-09-30
informational_warnings = ["unmaintained", "unsound"]
severity_threshold = "medium" # none | low | medium | high | critical
[output]
format = "terminal" # terminal | json | sarif
deny = ["warnings"] # mirrors --deny flags
show_tree = true
[database]
fetch = false # offline / air-gapped builds
stale = falsecargo audit fix
cargo audit fix --dry-run # preview
cargo audit fix # update Cargo.toml constraints + cargo updatecargo audit fix is experimental per rustsec-readme (opens in new window); it cannot resolve conflicting version constraints. Always run cargo test after.
Binary auditing
cargo install cargo-auditable
cargo auditable build --release # embeds Cargo.lock metadata
cargo audit bin target/release/my-app # audit the compiled binaryBinaries built without cargo-auditable have no embedded metadata and cannot be audited (rustsec-readme (opens in new window)).
GitHub Actions
The official rustsec/audit-check (opens in new window) action wraps cargo audit, fails the check run on any security advisory, and opens a GitHub Issue per advisory on scheduled runs:
on:
push:
paths: ['**/Cargo.toml', '**/Cargo.lock']
schedule:
- cron: '0 0 * * *'
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}SARIF upload alongside:
- run: cargo audit --format sarif > cargo-audit.sarif || true
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: cargo-audit.sarif }Rust-specific limitations
bundle-audit (Ruby)
bundler-audit (ba-readme (opens in new window)) scans Gemfile.lock against the ruby-advisory-db (opens in new window) - a community-maintained YAML corpus of CVE / GHSA / OSVDB advisories under gems/ (per-gem) and rubies/ (runtimes). It checks two classes of issues: vulnerable gem versions and insecure sources (http:// / git:// URIs).
gem install bundler-audit
bundle-audit update # one-time advisory-db clone/sync
bundle-audit check --update # refresh + scan (always --update in CI)
bundle-audit check --no-update # fully offline once the DB is syncedOutput flags (ba-readme (opens in new window)):
| Flag | Output |
|---|---|
--format json | JSON for multi-tool SCA triage |
--output FILE | Write to file (CI artifact) |
--gemfile-lock PATH | Non-default lockfile path |
.bundler-audit.yml waivers
Committed suppression file at the project root (ba-readme (opens in new window)); the ignore array takes CVE / GHSA / OSVDB identifiers. Every ignore MUST carry an inline justification - reachability finding, approver, re-review date; reviewers treat undocumented ignores as unapproved:
---
# Suppressions last reviewed: 2026-06-04 Re-review by: 2026-09-04
ignore:
# CVE-2024-1234: vulnerable function not reachable; foo-gem used only in
# test fixtures. Verified via grep + code review. Approved: alice@example.com
- CVE-2024-1234Per-run --ignore CVE-2024-1234 flags are for temporary workarounds only; they have no expiry mechanism - enforce a quarterly review cadence in process.
Rake integration
Per ba-readme (opens in new window):
require 'bundler/audit/task'
Bundler::Audit::Task.new
task default: %w[spec bundle:audit] # audit gates the local test runAdds rake bundle:audit and rake bundle:audit:update; bundle:audit exits non-zero on findings.
CI gating
bundle-audit check exits 0 clean, non-zero on findings (ba-readme (opens in new window)):
jobs:
bundle-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with: { bundler-cache: true }
- run: gem install bundler-audit
- run: bundle-audit check --update --format json --output bundle-audit.json
- uses: actions/upload-artifact@v4
if: always()
with: { name: bundle-audit, path: bundle-audit.json }Ruby-specific limitations
Deep reference for the npm-pip-maven-audit SKILL.md. SKILL.md keeps the primary scan command per ecosystem and the CI hashFiles gate; this file holds the speed/coverage tradeoffs, the Maven plugin block, the full suppression templates, and output aggregation.
Native audit vs unified scanner tradeoffs
| Property | Native audit | Snyk / OSV |
|---|---|---|
| Speed | <5s typical | 10s - 60s |
| DB coverage | Per-ecosystem only | Cross-ecosystem aggregated |
| False-positive triage | Per-ecosystem CLI | Unified config |
| Reachability analysis | None | None (most tools) |
| CI integration | Built into package manager | Per-tool action |
Native audit catches the high-confidence per-ecosystem feed quickly; the unified scanner catches cross-ecosystem aggregations and waivers.
Maven OWASP Dependency-Check plugin
Maven's audit story is the OWASP Dependency-Check plugin (no native mvn audit):
<!-- pom.xml -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>10.0.4</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<failBuildOnCVSS>7.0</failBuildOnCVSS>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
<formats>
<format>HTML</format>
<format>JSON</format>
<format>SARIF</format>
</formats>
</configuration>
</plugin>For Gradle: the same plugin via the org.owasp.dependencycheck Gradle plugin. Source: jeremylong.github.io/DependencyCheck/dependency-check-maven/.
Suppression justification templates
Justification is mandatory in the suppression file or audit-skip list:
<!-- dependency-check-suppressions.xml (Maven) -->
<suppress>
<notes>
Reason: log4j-core 2.14.x is bundled but not loaded at runtime
(verified via dependency tree analysis)
Approved-by: alice@example.com
Re-review-date: 2026-09-15
</notes>
<packageUrl regex="true">^pkg:maven/org\.apache\.logging\.log4j/log4j-core@2\.14\..*$</packageUrl>
<vulnerabilityName>CVE-2021-44228</vulnerabilityName>
</suppress>For ad-hoc CLI ignores (pip-audit --ignore-vuln, cargo audit --ignore), maintain a sibling AUDIT_IGNORES.md mapping each ID to reason + approver + re-review-date. Without the sibling file, the ignore is invisible to reviewers.
Cadence: every quarter, audit suppression entries; expired re-review dates remove entries.
Output aggregation
For downstream aggregation, output each tool's JSON to a stable filename:
npm audit --json > sca-npm.json || true # || true: don't fail before triage
pip-audit --format json --output sca-pip.json || true
mvn dependency-check:check -Dformats=JSON
cargo audit --json > sca-cargo.json || trueThe aggregation step normalizes each tool's schema + dedupes cross-tool findings.