BlogCI/CD

CI dependency scanning: pip-audit, npm audit, fail fast

Lockfile scanners like pip-audit and npm audit are free and fast — the hard part is a CI failure policy strict enough to matter that your team keeps on.

Jun 12, 2025·7 min readBeginner

The fastest way to kill a security gate is to make it slow and noisy. A dependency scanner that fails the build on a decade-old, unfixable CVE buried in a transitive package teaches developers exactly one skill: how to add allow_failure: true. A scan survives only if it's fast, deduplicated, and fails only on things a human can actually act on.

Point a scanner at your project and it reads the lockfiles to build a dependency inventory, then matches it against vulnerability feeds:

bash — trivy fslive
trivy fs --severity HIGH,CRITICAL --ignore-unfixed .
Detecting dependencies from package-lock.json ...
CRITICAL lodash 4.17.11 -> fixed in 4.17.21
HIGH axios 0.21.0 -> fixed in 0.21.2
2 actionable, all with a fix — the build fails, correctly
The gate
1
Commit
lockfiles
2
Resolve
full dep tree
3
Scan
match feeds
4
Gate
fixable high/crit

Fail only on what is actionable

Two flags do most of the work. --severity HIGH,CRITICAL drops the low-severity chatter, and --ignore-unfixed means the build only breaks when there's a version to bump to. A developer who hits this gate always has a clear next step — that's what keeps them from routing around it.

.gitlab-ci.yml
dep-scan:
image: aquasec/trivy:latest
script:
- trivy fs --scanners vuln
--severity HIGH,CRITICAL
--ignore-unfixed
--exit-code 1 .

Suppress with a reason, not a lower bar

There will be a genuine false positive, or a CVE you've assessed as not-exploitable in your context. Don't globally lower the severity — that blinds you to the next real one. Record a scoped, expiring exception with a reason, so the suppression is a reviewed decision instead of a silent hole.

.trivyignore
# CVE-2023-45853 — zlib, only reachable via a build-time tool.
# Reviewed 2025-06-10 by security. Re-check when base image bumps.
CVE-2023-45853
# Expired entries fail review — nothing hides here forever.
An unfixable CVE is a decision, not a pass
Ignoring a finding is a risk acceptance. Write down who accepted it and why, give it an expiry, and surface the list in review — the moment suppressions become invisible, the scanner is lying to you.

Generate an SBOM once, answer later

Have the pipeline emit a software bill of materials for every build. When the next Log4Shell drops, you answer 'are we affected, and where' by querying stored SBOMs in minutes — instead of re-scanning every repo you own under pressure.

Where this goes next

CI scanning catches what you're about to ship; it doesn't catch what's already running. Pair it with registry scanning on push and runtime scanning in the cluster, so a CVE disclosed after a build still gets flagged against the images you have in production.

Go deeper in a courseSecure CI/CD with GitLabBuild GitLab pipelines, then harden them with scanning and gates.View course

Related posts