Skip to content
Back to field notes
DependenciesJune 6, 20266 min read

npm audit before launch: what the output actually means

A red npm audit count is not automatically a launch blocker; this note shows the indie developer how to read the output and make a go or no-go call before sharing the URL.

Dependenciesnpm auditSupply chain

A red audit count is not yet a launch decision

You run npm audit before launch, the terminal prints "14 vulnerabilities (3 high, 2 critical)", and now you are stuck. Ship anyway? Run npm audit fix --force and risk breaking the build an hour before you share the URL? The number on its own does not answer that. The risk this note addresses is the wrong reaction to the output: either shipping a genuinely exploitable critical because the count looked like noise, or panic-upgrading your whole tree and breaking a working app over a finding that can never run in production.

Here is what the audit actually reports, stated plainly. npm audit cross-references your installed dependency tree against the GitHub Advisory Database and lists packages whose installed version falls in a vulnerable range. That is a real, observable fact about your lockfile. What it does not report is whether the vulnerable code path is reachable in your app, whether the input that triggers it ever arrives from a user, or whether the package is even shipped to production at all.

  • Observable from the audit: which installed package versions match a known advisory, the advisory severity, and whether the dependency is direct or transitive.
  • Not observable from the audit: exploitability in your specific app, whether the flawed function is ever called, and whether a devDependency finding touches your running site at all.
  • The gap between those two lists is exactly where the go or no-go decision lives.

Why AI-built apps surface a wall of findings

AI scaffolds pull in a lot of dependencies fast. A single "build me a dashboard with charts and auth" prompt can install a chart library, a date library, a UI kit, and an auth helper, each dragging in its own transitive tree. You did not choose most of those packages, and you never saw the install resolve. By the time you run the audit, you are looking at a tree you did not assemble by hand.

Two patterns make the output look worse than the actual exposure. First, AI tools tend to scaffold the full dev toolchain (bundlers, test runners, linters), so a large share of findings sit in devDependencies that never ship in your client bundle or server build. Second, generated apps are often deployed before anyone runs the audit even once, so the public URL exists while the dependency tree is still unreviewed. The count is high not because the app is uniquely unsafe, but because nobody triaged it yet.

  • The framework default (Vite, Next.js, and similar) brings a heavy dev toolchain, inflating the devDependency portion of the count.
  • The skipped review loop means no human ever read the install output, so transitive packages arrive unexamined.
  • A red audit number is a signal to triage, not a verdict. The work is separating the findings that reach production from the ones that cannot.

Triage the output before you touch a fix

You can sort the findings on your own app in a few minutes, with no attack and no guessing. Run the audit and read it along three axes.

First, direct versus transitive. Run npm audit to see the advisory list, then npm ls followed by the package name to see why a transitive package is installed and which dependency pulled it in. A direct dependency you import yourself is more likely to expose the vulnerable code path than a transitive package buried four levels down that your code never calls.

Second, runtime versus dev. Run npm audit --omit=dev (or npm audit --production on older npm) to drop devDependencies from the report. A finding that vanishes when you omit dev tooling generally does not ship to your users; it lives in your build or test environment. That is real context, not an all-clear, since a compromised build tool is still a supply-chain concern, but it changes the urgency.

Third, severity versus exploitability. Severity is the advisory's worst-case rating, assigned without knowing your app. Read the advisory itself: a prototype-pollution or ReDoS issue in a parser you only feed trusted, hardcoded input is a different risk than the same issue in a parser that handles raw request bodies. Severity tells you how bad it could be; only your call graph tells you whether it can happen here.

  • Suspicious result: a critical or high in a direct, runtime dependency that handles user-supplied input. Treat that as a launch blocker until you have updated or confirmed it is unreachable.
  • Lower-urgency result: a high that only appears without --omit=dev, or a transitive package your code never invokes. Note it, plan the fix, but it need not hold the launch on its own.
  • Either way, write down the decision per finding. "Deferred because devDependency only" is a defensible call; "ignored the whole list" is not.

Fix without breaking the working app

Once you know which findings matter, fix those, not the whole tree. The order of operations protects a launch.

Start with npm audit fix, which upgrades within your declared semver ranges. These are usually patch or minor bumps that resolve the advisory without API changes, so the blast radius is small. Run your build and your app after it, because even a patch can shift behavior. The dangerous tool is npm audit fix --force: it installs out-of-range major versions to clear advisories, which means breaking changes land in packages your app actually depends on. It can clear the audit and break the build in the same command, which is the worst possible outcome an hour before launch.

For a finding that --force would resolve only by a major upgrade, prefer to bump that one package deliberately, read its changelog, and test, rather than letting --force rewrite the tree. For a transitive package with no available fix, an overrides entry in package.json can pin the resolved version, but verify the app still works afterward, since you are overriding what the dependency expects.

  • Run npm audit fix first; build and smoke-test the app before trusting it.
  • Avoid npm audit fix --force as a reflex; it trades a clean audit for unverified breaking changes. If you do run it, do it on a branch, never minutes before sharing the URL.
  • What the fix does not cover: clearing the audit does not prove the rest of your dependency tree is safe. The advisory database only knows about disclosed issues, so a clean audit means "no known advisories today", not "no vulnerabilities".

npm audit checks your dependency tree against a database of disclosed advisories. It does not see undisclosed flaws, it does not confirm the vulnerable code is reachable in your app, and a passing audit is a point-in-time snapshot that a new advisory can invalidate tomorrow. Re-run it close to launch, not once at the start.

Where VibeCodeGuard fits, and what it does not prove

The audit is a local check against your installed tree. VibeCodeGuard's Launch Check works from the other side: it scans the deployed public surface and can flag outdated dependency signals that are observable from outside, such as a library version exposed in the served client bundle. That is a useful cross-check, since the version your audit reads from the lockfile should match what actually shipped, but it is a different vantage point. Treat the local audit as the place you interpret severity and reachability, and treat the public scan as confirmation of what reached production.

What the Launch Check does not do is run npm audit for you, read your full dependency tree, or judge exploitability. It is not a code review and it does not see devDependencies or packages that never appear on the public surface. The triage and the fix decision stay with you and your terminal.

Next step: run npm audit, then npm audit --omit=dev, and decide each runtime finding go or no-go before you fix anything. Once the tree is settled and deployed, run a focused Launch Check against the public URL so an outdated library that actually shipped shows up in your scan and not in someone else's.

A clean Launch Check on the public surface does not prove your dependencies are safe; it sees what an outside visitor can reach, not your whole tree or whether a vulnerable path is exploitable. The audit interpretation in this note is the local context that should happen before the final scan, not a replacement for it.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.