Closing the Silent CVE Gap: How We Made npm audit a Real Security Gate
Closing the Silent CVE Gap: How We Made npm audit a Real Security Gate
Release: v0.1.141 · Type: CI/Security pipeline hardening · Ticket: SEC-25
The Problem
Sanctions Monitor is a compliance platform. Its entire purpose is to detect risk — so it needs to hold itself to the same standard. Until this release, it did not.
The dependency-audit CI job contained a single character that undermined the entire control:
# Before — .github/workflows/ci.yml
- name: Audit dependencies
run: npm audit --audit-level=high || true
# Non-blocking for now; logs vulnerabilities for review
The || true suffix means: whatever the exit code of npm audit, tell the shell it succeeded. High and critical CVEs in production dependencies were printed to the CI log and then completely ignored. There was no gate. A supply-chain vulnerability could have shipped to production without a single warning in the merge process.
The Fix
Three focused changes to the dependency-audit job in .github/workflows/ci.yml:
# After — .github/workflows/ci.yml
- name: Audit production dependencies for critical CVEs
# SEC-25: Blocking audit — critical findings in production dependencies
# will fail CI and prevent deployment.
# --omit=dev excludes dev-only tooling (never shipped to production)
# to reduce noise from packages like eslint plugins, test runners, etc.
# Threshold is set to "critical" (not "high") because several transitive
# dependencies report high-severity findings that have no upstream fix
# available yet and do not represent an exploitable attack surface in
# this server-rendered Next.js application.
# To suppress a known-acceptable finding, add an entry to .nsprc or
# run: npm audit fix --force (only after security team sign-off).
run: npm audit --audit-level=critical --omit=dev
continue-on-error: false
Change 1 — Remove || true
The most important change. npm audit now exits with a non-zero code when it finds critical findings, which causes the CI job to fail and blocks the PR from merging.
Change 2 — Add --omit=dev
Dev dependencies are installed on developer machines and CI runners, but are never bundled into production builds or shipped to users. Auditing them produces noise and surfaces findings that are not part of the real attack surface.
--omit=dev scopes the audit to packages that actually run in production: the Next.js application, its runtime dependencies, and anything else that ends up deployed.
Change 3 — Add continue-on-error: false
This is explicit documentation of intent. GitHub Actions jobs do not continue on error by default, but adding continue-on-error: false makes it unambiguous to future maintainers that this job is meant to be a hard gate — and makes any future attempt to re-relax it visible in a diff.
Why --audit-level=critical and not --audit-level=high?
Several transitive dependencies in the dependency tree currently report high-severity findings for which no upstream fix exists. After review, these do not represent an exploitable attack surface in a server-rendered Next.js application that never exposes the affected code paths to untrusted input.
Blocking on high at this moment would break every CI run for unfixable issues — the opposite of a useful gate. The threshold is set to critical as the meaningful, actionable line. The intent is to raise this to high as upstream fixes become available.
Handling a Flagged Finding
If CI fails because npm audit detects a critical CVE, there are two legitimate paths forward:
Option A — Fix it
npm audit fix
# or, for breaking changes:
npm audit fix --force
Option B — Accept the risk with documentation
- Have the security team review the finding.
- Document the decision in a comment, ticket, or ADR.
- Add a scoped exception to
.nsprc.
⚠️ Never re-add
|| trueto bypass the check. That removes the gate entirely for every future finding, not just the one being evaluated.
Security Impact
| Before | After | |
|---|---|---|
| Critical CVE in prod dep | Logged, build passes ✅ | CI fails, PR blocked 🚫 |
| High CVE in prod dep | Logged, build passes ✅ | Logged, build passes ✅ |
| Any CVE in dev-only dep | Logged, build passes ✅ | Not audited (excluded) |
| Explicit blocking intent | No | Yes (continue-on-error: false) |
For a platform whose mission is sanctions screening and compliance, meaningful security gates are not optional. This fix brings the CI pipeline in line with that mission.