0% прочитано

npm audit in a Real Laravel/Vite Project: Why I Did Not Run npm audit fix --force

In my Laravel/Vite project, npm audit reported 9 vulnerabilities, including 2 critical and 5 high. The fastest-looking response was npm audit fix --force, but I deliberately did not use it. I first inspected the dependency tree, separated direct and transitive dependencies, checked development versus production exposure, and only then applied safe upgrades and repeated the audit and build verification.

14 вересня 2026 р. 10 хв читанняDevOps

While reviewing frontend dependencies in ICanUp, I got a result serious enough to make a one-command fix look very tempting.

npm audit reported 9 vulnerabilities: 1 low, 1 moderate, 5 high, and 2 critical.

npm immediately offered automated remediation commands, but I deliberately did not start with npm audit fix --force.

Before changing the dependency tree, I wanted to understand which packages were vulnerable, where they came from, and what an automated fix would actually change.

Initial npm audit result
9 vulnerabilities 1 low1 moderate5 high2 critical
Severity in npm audit is an important signal, but it does not automatically describe how exploitable a vulnerability is in one specific application. I first inspect the dependency path and execution context.
npm audit in a Laravel and Vite project showing critical and high vulnerabilities before dependency analysis
Instead of running npm audit fix --force, I first split the audit report into direct dependencies, transitive dependencies, execution context, and safe upgrade paths.

First I Capture the Actual Audit Report

I started with a repeatable view of the current dependency state rather than an automatic fix.

This matters because npm audit reflects the real package-lock.json, not only the package names listed in package.json.

BASH - Run the security audit
npm audit
BASH - Save a machine-readable audit snapshot
npm audit --json > /tmp/npm-audit.json

During the work, the number of findings changed together with the dependency tree. A later snapshot showed 7 vulnerabilities, and another later one showed 4.

I therefore do not treat the vulnerability count as a permanent property of the project. It describes a particular lockfile at a particular point in time.

I Did Not Start with npm audit fix --force

The --force option is attractive because it promises to remove more findings automatically.

It can also allow updates beyond the compatible versions of top-level dependencies.

In a production project, a security fix can therefore turn into a framework or tooling migration at the same time.

BASH - The command I did not run first
# I did NOT start with this:npm audit fix --force
My goal was not to reach "0 vulnerabilities" at any cost. The goal was to reduce real risk without breaking the build, runtime, or development workflow through an uncontrolled major upgrade.

Direct and Transitive Dependencies Need Different Reasoning

An audit can report a package I never added directly to package.json.

That is expected because many vulnerabilities arrive through transitive dependencies.

The next question for each finding is therefore: which dependency pulled this package into the tree?

BASH - Inspect dependency paths for the findings
npm ls \  shell-quote \  concurrently \  vite \  axios \  esbuild \  form-data \  immutable
BASH - Explain why a package exists in the tree
npm explain shell-quote npm explain vite

The Critical shell-quote Finding Was Not a Standalone Direct Dependency

One of the most important findings involved shell-quote with critical severity.

But shell-quote was not a package I had deliberately added to application code. In the dependency tree it was pulled in through concurrently@9.2.1.

The remediation decision therefore belongs higher in the dependency tree, not necessarily at the vulnerable package itself.

Simplified dependency path
ICanUp  |  vconcurrently@9.2.1  |  vshell-quote  |  vcritical advisory

Vite Was Part of the Audit Report Too

The report also contained a high-severity finding involving vite.

In a Laravel/Vite project, that is a different class of dependency. Vite is central to the frontend toolchain, so an upgrade needs verification through a real production build, not only another audit.

BASH - Check installed and available Vite versions
npm ls vite npm view vite version

The Audit Covered More Than One Part of the Toolchain

During the investigation I also saw findings related to Axios, esbuild, form-data, and immutable.

This is another reason not to respond to the entire report with one command.

Different packages can have different dependency paths, execution contexts, and upgrade costs.

Package / area

What I inspect

shell-quote

Which parent pulls it in and whether updating the parent is enough

concurrently

Whether it is a direct dev dependency and which update changes the subtree

vite

Build-tool compatibility after the upgrade

Axios / form-data

Dependency path and actual execution context

esbuild

Build-time exposure and the version used by the toolchain

immutable

Whether it is direct, transitive, or no longer required

Severity and Production Exposure Are Not the Same Thing

A critical advisory always deserves attention, but the severity label alone does not describe one project's architecture.

In a Laravel/Vite application, some Node packages run only during development or build and are not present as a Node runtime on the production web server.

Other packages may reach the browser bundle or be used by application code.

I do not use this distinction to dismiss dev vulnerabilities. I use it to prioritize and remediate them correctly.

BASH - Inspect the non-dev dependency slice
npm audit --omit=dev
npm audit --omit=dev does not replace the complete audit. It provides one useful risk slice, while build-time and development vulnerabilities still require review.

I Inspect the Planned Fix Before Applying It

There is a safer step between doing nothing and running npm audit fix --force.

I first inspect what npm can repair without allowing an aggressive major upgrade.

BASH - Preview compatible remediation
npm audit fix --dry-run

From there I can evaluate direct dependencies separately and decide whether a specific package needs a controlled manual upgrade.

A Safe Fix and a Forced Fix Are Different Decisions

Approach

What it means to me

npm audit fix

Try compatible dependency updates and review the diff

Manual package upgrade

Change one direct dependency deliberately

npm audit fix --force

Allow more aggressive changes that may require migration work

Leave a finding temporarily

Only with understood exposure, documented reasoning, and a plan

I Review the Diff After Every Dependency Change

An automated security command does not remove the need for review.

Even when most changes are in package-lock.json, I want to know which top-level and transitive versions actually moved.

BASH - Review the dependency diff
git diff -- \  package.json \  package-lock.json
BASH - Check the direct dependency tree after changes
npm ls --depth=0

I Repeat the Audit After Every Meaningful Change

I do not consider the work finished simply because npm install succeeds after dependency changes.

I run the same audit again and compare the new state with the previous snapshot.

BASH - Repeat the audit after dependency changes
npm audit

One later verification snapshot looked very different:

A later audit snapshot
4 vulnerabilities 1 moderate3 high

There were no critical findings in that snapshot.

For me, that is more useful progress than blindly forcing npm to rewrite the dependency tree into any state that happens to display zero.

Zero Findings Is Not the Only Completion Criterion

Security work does not always reach found 0 vulnerabilities in one commit.

An advisory may remain in a transitive dependency that does not yet have a compatible upstream fix.

In that case, I want to know where the dependency is used, whether it is production-reachable, and which future upgrade removes it.

An unresolved finding with no explanation is a bad result. An unresolved finding with a verified dependency path, understood exposure, and an upgrade plan is managed technical debt.

A Security Fix Still Has to Pass the Build

After dependency changes, the audit report is not my only verification.

Vite, esbuild, and other frontend dependencies directly affect the build pipeline.

BASH - Rebuild production frontend assets
npm run build

If an update makes the audit look better but production assets no longer build, the work is not finished.

Then I Verify the Application, Not Only the Package Manager

  • Production build finishes successfully.
  • The existing frontend tests still pass.
  • Public pages load correctly.
  • The admin interface still works.
  • Vite assets are loaded correctly.
  • SSR still starts when it uses the same frontend build.
  • The browser console has no new runtime errors.
  • A repeated npm audit shows the expected new state.

npm audit fix --force Could Have Become a Separate Migration

If eliminating an advisory requires moving to an incompatible major version of a direct dependency, that is no longer just a security patch.

It is a migration that deserves release notes review, configuration checks, and regression testing.

I do not want to hide that migration inside one automatic security command.

Different remediation paths
Security finding      |      vDoes a compatible fix exist?   /               \ yes                no  |                  |  v                  vsafe update       evaluate major upgrade  |                  |  v                  vaudit + build     migration + regression tests

What I Do Not Do After a Scary Audit Report

  • Do not reflexively run npm audit fix --force.
  • Do not judge risk only by the total vulnerability count.
  • Do not treat every transitive package as a direct application dependency.
  • Do not dismiss a critical finding only because it is under devDependencies.
  • Do not upgrade every package at once without reviewing the diff.
  • Do not treat a successful npm install as sufficient regression verification.
  • Do not chase zero findings through an uncontrolled major upgrade.
  • Do not leave unresolved high or critical findings without an explanation.

My npm audit Checklist

  • Capture the full npm audit report.
  • Keep a JSON snapshot when comparing states.
  • Identify direct and transitive dependency paths.
  • Inspect production and development execution contexts separately.
  • Review advisories and affected versions for critical and high findings.
  • Preview npm audit fix --dry-run.
  • Apply compatible updates in controlled steps.
  • Review the package.json and package-lock.json diff.
  • Repeat npm audit.
  • Run the production build and regression checks.
  • Document remaining findings together with their dependency path and plan.

This Changed How I Think About Automated Security Fixes

It is easy to think of npm audit as a command that should simply end with a green status.

In practice, the audit is the beginning of an investigation, not the finished solution.

That is especially visible in a Laravel/Vite project where the Node dependency tree contains browser dependencies, build tooling, and development utilities at the same time.

npm audit tells me where to investigate. It cannot decide which breaking upgrade is safe for my project.

What I Kept for Next Time

  • Capture an audit snapshot before changing dependencies.
  • Prioritize critical severity, but always inspect the dependency path.
  • Separate direct from transitive dependencies.
  • Separate build-time, development, and production exposure.
  • Prefer compatible fixes first and treat major upgrades as separate work.
  • Use dry-run before changes and Git diff after them.
  • Repeat the audit after every dependency update.
  • A security update is not complete until the production build passes.
  • Do not demand zero findings at the cost of an uncontrolled migration.

Conclusion

The initial 9 vulnerabilities in ICanUp, including 2 critical and 5 high, were definitely not a signal to ignore the problem.

But they were not a signal to immediately run npm audit fix --force either.

I inspected dependency paths, found the critical shell-quote issue through concurrently, reviewed Vite and the other findings separately, applied controlled updates, and repeated both the audit and production build after the changes.

A later audit snapshot showed 4 remaining vulnerabilities with no critical findings.

For me, a good security fix is not the shortest command. It is a change where I understand what moved, which risk was reduced, and whether the application still behaves correctly afterward.