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.
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 is fast, deduplicated, and fails only on things a human can actually act on. Lockfile scanners like pip-audit, npm audit, and Trivy fs are free and fast — the hard part is a failure policy strict enough to matter that your team keeps it on.
This note wires a filesystem scan into GitLab CI, gates on fixable HIGH and CRITICAL findings, and records suppressions with reasons and expiry. For the full pipeline hardening track, see Secure CI/CD with GitLab.
If the gate has no clear fix path, developers route around it. --ignore-unfixed is the difference.
trivy fs --severity HIGH,CRITICAL --ignore-unfixed .Detecting dependencies from package-lock.json ...CRITICAL lodash 4.17.11 -> fixed in 4.17.21HIGH axios 0.21.0 -> fixed in 0.21.22 actionable, all with a fix — the build fails, correctlyFail 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 is a version to bump to. A developer who hits this gate always has a clear next step — bump the dependency, rebuild, merge. That is what keeps them from routing around it with allow_failure: true.
dep-scan:stage: testimage: aquasec/trivy:0.53.0script:- trivy fs --scanners vuln--severity HIGH,CRITICAL--ignore-unfixed--exit-code 1 .rules:- if: $CI_PIPELINE_SOURCE == "merge_request_event"
For Python-only repos, pip-audit -r requirements.txt or scanning against poetry.lock is equally valid and sometimes faster. The policy matters more than the tool — same severity filter, same fixable-only gate, same MR visibility.
Suppress with a reason, not a lower bar
There will be a genuine false positive, or a CVE you have assessed as not-exploitable in your context. Do not 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.
# 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.
Generate an SBOM once, answer later
Have the pipeline emit a software bill of materials for every build — trivy fs --format cyclonedx or syft . works on the same checkout. 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 are about to ship; it does not catch what is 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. The Secure CI/CD with GitLab course covers scanning, signing, SBOMs, and policy gates end to end.
Go deeper in a courseSecure CI/CD with GitLabBuild GitLab pipelines, then harden them with scanning and gates.View course