You run npm audit. It prints 47 vulnerabilities in angry red. You scroll through
two screens of them, and slowly realize: a dozen are in your build tooling and
never ship, half have no fix you can apply, and the rest are low. The one thing
that actually matters — a critical in a production dependency with a one-command
fix — is buried somewhere in the middle.
So you do what everyone does. You stop reading. Maybe you slap || true on the CI
step. And that is exactly the habit that lets a real critical sail through later.
Dan Abramov called npm audit "broken by design"
back in 2021, and the core complaint still stands: it reports your entire
dependency tree's advisory history, with no sense of what's reachable, fixable, or
worth your attention. The signal is in there. It's just drowned.
What I wanted
Not another security platform. Not a config file full of allow-list rules. Not an
interactive wizard. I just wanted the short list: production deps, high or
critical, with a fix available — the ~20% of the report I'd actually act on this
afternoon.
So I built auditclean. It's a single zero-dependency CLI that runs your
npm audit, parses it, and prints only what survives that filter.
npx auditclean
auditclean — 2 of 47 advisories need action (≥ high, production, fixable)
● critical lodash <=4.17.20 Prototype Pollution in lodash → npm audit fix
● high minimist <1.2.6 Prototype Pollution in minimist → upgrade minimist to 1.2.8
45 hidden (below high · dev-only · no fix). See everything: auditclean --all
That's it. The 45 you were going to ignore anyway are gone. The 2 that need a
human are right there, each with its fix spelled out.
How it works
By default it runs npm audit --omit=dev --json for you — so "production only"
isn't a heuristic, it's npm's own dependency resolution. Then it filters to
high+ severity with a fix, and normalizes the fix advice (including
isSemVerMajor upgrades, which it flags as (breaking) so you know which ones
aren't a free npm audit fix).
You can widen or narrow it:
auditclean --level critical # only criticals
auditclean --include-unfixable # show the no-fix ones too
auditclean --all # everything npm audit would, just cleaned up
Or feed it a report you already have:
npm audit --json | auditclean
It reads both npm audit JSON schemas — the v2 vulnerabilities shape (npm 7+)
and the old v1 advisories shape (npm 6) — because CI images in the wild still
run both.
The CI payoff
Plain npm audit exits non-zero on anything, which is why so many pipelines
ignore it. auditclean exits non-zero only when something actionable is left,
so it gates on signal:
# fails the build only on production, high+, fixable vulnerabilities
- run: npx auditclean
exit 0 nothing actionable at the threshold
exit 1 actionable vulnerabilities found
exit 2 error (npm missing, unparseable input)
A few design notes
-
Zero dependencies, on purpose. It shells out to the
npmyou already have and parses JSON with the standard library. A security tool that drags in 40 transitive deps of its own is its own punchline. -
--omit=devover guessing. The npm 7+ report doesn't tag dev-vs-prod per vuln, so rather than guess from the dependency graph, auditclean just asks npm to omit dev deps. If you pipe a full report in, pipenpm audit --omit=dev --json. -
It hides, it doesn't lie. Every run tells you how many it hid and how to see
them (
--all). Silent filtering is just a different kind of broken.
Try it / break it
- Node: https://github.com/jjdoor/auditclean
- Python: https://github.com/jjdoor/auditclean-py (same filter, shells out to npm)
npx auditclean # Node — zero deps
pip install auditclean # Python — pure stdlib
It's MIT and tiny. I'd love to hear which of your "47 advisories" auditclean
correctly shrinks to 2 — and where it gets the call wrong.
How do you deal with npm audit noise today — || true, a allow-list, a paid
scanner, or just not looking?
Top comments (0)