<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: neve7r</title>
    <description>The latest articles on DEV Community by neve7r (@neve7er).</description>
    <link>https://dev.to/neve7er</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3912490%2F41ae4161-56d3-4685-922c-f351a17d4e45.png</url>
      <title>DEV Community: neve7r</title>
      <link>https://dev.to/neve7er</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neve7er"/>
    <language>en</language>
    <item>
      <title>"Why I stopped trusting npm audit (and built my own)"</title>
      <dc:creator>neve7r</dc:creator>
      <pubDate>Mon, 04 May 2026 17:42:52 +0000</pubDate>
      <link>https://dev.to/neve7er/why-i-stopped-trusting-npm-audit-and-built-my-own-12d3</link>
      <guid>https://dev.to/neve7er/why-i-stopped-trusting-npm-audit-and-built-my-own-12d3</guid>
      <description>&lt;p&gt;Generate a CycloneDX SBOM and deterministic, audit-ready risk report from your package-lock.json.&lt;/p&gt;

&lt;p&gt;You run npm audit. It says “47 vulnerabilities.”&lt;/p&gt;

&lt;p&gt;Cool.&lt;/p&gt;

&lt;p&gt;Which ones actually matter?&lt;/p&gt;

&lt;p&gt;The one in your production bundle?&lt;br&gt;
The dev-only Jest dependency?&lt;br&gt;
The transitive package you didn’t even know existed?&lt;/p&gt;

&lt;p&gt;You don’t know.&lt;/p&gt;

&lt;p&gt;So you either:&lt;/p&gt;

&lt;p&gt;Ignore everything → ship anyway&lt;br&gt;
Or block everything → break your team&lt;/p&gt;

&lt;p&gt;Either way, you lose signal.&lt;/p&gt;

&lt;p&gt;The real problem isn’t vulnerabilities — it’s decision-making&lt;/p&gt;

&lt;p&gt;Most tools answer:&lt;/p&gt;

&lt;p&gt;“What is wrong?”&lt;/p&gt;

&lt;p&gt;They don’t answer:&lt;/p&gt;

&lt;p&gt;“What should I do about it?”&lt;br&gt;
“Can I prove that decision later?”&lt;/p&gt;

&lt;p&gt;That last one is the real problem.&lt;/p&gt;

&lt;p&gt;Enter: audit-ready&lt;/p&gt;

&lt;p&gt;Instead of scores, it gives you decisions.&lt;/p&gt;

&lt;p&gt;Deterministic. Reproducible. Auditable.&lt;/p&gt;

&lt;p&gt;🔑 reasonCode replaces CVSS&lt;/p&gt;

&lt;p&gt;Every dependency gets exactly one label:&lt;/p&gt;

&lt;p&gt;DEV_DEPENDENCY_ONLY&lt;br&gt;
OPTIONAL_DEPENDENCY&lt;br&gt;
TRANSITIVE_NO_EXPLOIT&lt;br&gt;
DIRECT_UNPATCHED&lt;br&gt;
NO_KNOWN_VULNERABILITY&lt;br&gt;
EXEMPTED&lt;/p&gt;

&lt;p&gt;No interpretation required.&lt;/p&gt;

&lt;p&gt;CI becomes trivial&lt;br&gt;
npx audit-ready scan --fail-on DIRECT_UNPATCHED&lt;br&gt;
Exit 0 → safe&lt;br&gt;
Exit 1 → actionable issue&lt;/p&gt;

&lt;p&gt;Not “7 high vulnerabilities.”&lt;br&gt;
👉 A clear, enforceable rule&lt;/p&gt;

&lt;p&gt;🧠 The constraint that shapes everything&lt;/p&gt;

&lt;p&gt;Same package-lock.json → identical output. Always.&lt;/p&gt;

&lt;p&gt;How that’s enforced&lt;/p&gt;

&lt;p&gt;Core logic has hard constraints:&lt;/p&gt;

&lt;p&gt;no Date&lt;br&gt;
no Math.random()&lt;br&gt;
no process.env&lt;br&gt;
no I/O&lt;/p&gt;

&lt;p&gt;And yes, it’s enforced by test:&lt;/p&gt;

&lt;p&gt;const banned = ['Date', 'Date.now()', 'Math.random()', 'process.env'];&lt;br&gt;
expect(found).toHaveLength(0);&lt;/p&gt;

&lt;p&gt;If determinism breaks → build fails.&lt;/p&gt;

&lt;p&gt;⚙️ The engine is intentionally simple&lt;br&gt;
for (const rule of rules) {&lt;br&gt;
  if (rule.match(node)) {&lt;br&gt;
    return { ...node, reasonCode: rule.reasonCode }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;No scoring. No heuristics.&lt;/p&gt;

&lt;p&gt;👉 First match wins&lt;/p&gt;

&lt;p&gt;Priority    Rule&lt;br&gt;
1   NO_KNOWN_VULNERABILITY&lt;br&gt;
2   DEV_DEPENDENCY_ONLY&lt;br&gt;
3   OPTIONAL_DEPENDENCY&lt;br&gt;
4   TRANSITIVE_NO_EXPLOIT&lt;br&gt;
5   DIRECT_UNPATCHED&lt;/p&gt;

&lt;p&gt;Order = logic.&lt;/p&gt;

&lt;p&gt;🧾 Output you can actually use&lt;br&gt;
CycloneDX 1.5 SBOM&lt;br&gt;
Human-readable report&lt;br&gt;
SARIF (GitHub Security)&lt;/p&gt;

&lt;p&gt;Everything tied to reasonCode.&lt;/p&gt;

&lt;p&gt;🔐 Security: this tool audits itself&lt;/p&gt;

&lt;p&gt;If you’re generating audit artifacts, your tool has to be trustworthy.&lt;/p&gt;

&lt;p&gt;Here’s what that means in practice:&lt;/p&gt;

&lt;p&gt;No environment access&lt;/p&gt;

&lt;p&gt;The core engine literally cannot read:&lt;/p&gt;

&lt;p&gt;environment variables&lt;br&gt;
system time&lt;br&gt;
runtime context&lt;/p&gt;

&lt;p&gt;👉 Output depends only on input + tool version&lt;/p&gt;

&lt;p&gt;Deterministic PURL generation&lt;/p&gt;

&lt;p&gt;Standard encoders (encodeURIComponent, URL) can differ across Node versions.&lt;/p&gt;

&lt;p&gt;So PURLs are built manually.&lt;/p&gt;

&lt;p&gt;👉 Same package → same PURL → always&lt;/p&gt;

&lt;p&gt;Schema validation (input + output)&lt;br&gt;
.audit-policy.json → validated before scan&lt;br&gt;
SBOM → validated before write&lt;/p&gt;

&lt;p&gt;If validation fails:&lt;/p&gt;

&lt;p&gt;👉 Nothing is written&lt;/p&gt;

&lt;p&gt;Immutable output&lt;br&gt;
All models are readonly&lt;br&gt;
Everything is Object.freeze()d&lt;/p&gt;

&lt;p&gt;👉 No silent mutation&lt;br&gt;
👉 No post-processing surprises&lt;/p&gt;

&lt;p&gt;Exceptions cannot live forever&lt;/p&gt;

&lt;p&gt;Every exception requires:&lt;/p&gt;

&lt;p&gt;a reason&lt;br&gt;
an expiration date&lt;/p&gt;

&lt;p&gt;Expired?&lt;/p&gt;

&lt;p&gt;audit-ready audit-exceptions&lt;/p&gt;

&lt;p&gt;👉 exit 1&lt;/p&gt;

&lt;p&gt;No silent ignores.&lt;/p&gt;

&lt;p&gt;Network safety by design&lt;/p&gt;

&lt;p&gt;Only one external call:&lt;/p&gt;

&lt;p&gt;👉 OSV API with PURLs&lt;/p&gt;

&lt;p&gt;If it fails:&lt;/p&gt;

&lt;p&gt;SBOM still generated&lt;br&gt;
tool exits with code 2&lt;br&gt;
no retries, no stale cache&lt;br&gt;
Self-audit (this is rare)&lt;br&gt;
audit-ready audit-self&lt;/p&gt;

&lt;p&gt;The tool runs its own pipeline on itself.&lt;/p&gt;

&lt;p&gt;Same code. Same rules.&lt;/p&gt;

&lt;p&gt;👉 If it lies, it exposes itself.&lt;/p&gt;

&lt;p&gt;⚠️ What this tool does NOT do&lt;br&gt;
No AI explanations&lt;br&gt;
No “smart” guessing&lt;br&gt;
No monorepo support (yet)&lt;br&gt;
No caching (Phase 3)&lt;br&gt;
npm only&lt;/p&gt;

&lt;p&gt;If a case isn’t covered:&lt;/p&gt;

&lt;p&gt;👉 it fails loudly&lt;/p&gt;

&lt;p&gt;Why this matters&lt;/p&gt;

&lt;p&gt;This isn’t about better scanning.&lt;/p&gt;

&lt;p&gt;It’s about:&lt;/p&gt;

&lt;p&gt;reproducible decisions&lt;br&gt;
CI you can trust&lt;br&gt;
audit trails you can defend&lt;br&gt;
🧪 Try it&lt;br&gt;
npx audit-ready@beta scan --dry-run&lt;br&gt;
🧭 Status&lt;br&gt;
Phase 1: SBOM + triage ✅&lt;br&gt;
Phase 2: policy + exceptions ✅&lt;br&gt;
Phase 3: caching + performance 🚧&lt;/p&gt;

&lt;p&gt;Production release planned after Phase 3.&lt;/p&gt;

&lt;p&gt;💬 Looking for feedback&lt;br&gt;
weird dependency graphs&lt;br&gt;
incorrect classifications&lt;br&gt;
CI edge cases&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/neve7er/audit-ready" rel="noopener noreferrer"&gt;https://github.com/neve7er/audit-ready&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final thought&lt;/p&gt;

&lt;p&gt;Most tools try to be smart.&lt;/p&gt;

&lt;p&gt;This one tries to be predictable.&lt;/p&gt;

&lt;p&gt;Because in security:&lt;/p&gt;

&lt;p&gt;predictability beats intelligence.&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>npm</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
