Safeguard
Open Source

npm run dev: What It Does and How to Run It Safely

npm run dev starts your project's development server via a script in package.json. Here is what actually happens under the hood and the security risks worth knowing.

Marcus Chen
DevSecOps Engineer
6 min read

npm run dev executes the dev script defined in your project's package.json, which almost always starts a local development server with hot reloading. It's one of the first commands anyone learns in the JavaScript ecosystem, but what it actually does depends entirely on how that script is written — and running it means executing whatever commands the project (and its dependencies) put there. That last part is where the security angle comes in, because npm run is a code-execution command, not a fixed built-in.

What actually happens when you run it

npm run <name> looks up a key in the scripts object of your package.json and runs its value as a shell command. So npm run dev runs whatever string is set for dev:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "start": "node server.js"
  }
}

With that config, npm run dev runs vite, which starts Vite's development server. In other projects it might be next dev, webpack serve, nodemon server.js, or a chain of commands joined with &&. There's nothing magic about the name dev — it's a convention, not a reserved word. (By contrast, start, test, stop, and restart are special enough that npm start works without run; for everything else you need npm run.)

When npm runs a script, it also puts your project's node_modules/.bin on the PATH, which is why dev: "vite" finds the locally-installed Vite binary rather than needing a global install. That's a convenience — and, as we'll see, part of the risk surface.

Reading a script before you run it

Because npm run dev runs arbitrary commands, the safe habit — especially with an unfamiliar repository — is to read the script first. You don't need to run anything to inspect it:

# List all scripts defined in the project
npm run

# Or just look at the scripts block directly
cat package.json

npm run with no argument prints every available script and its command. Skim the dev entry and any pre/post hooks before executing. This takes ten seconds and tells you exactly what's about to run on your machine.

The lifecycle-script risk

Here's the part many developers don't know: npm runs certain scripts automatically, without you naming them. If a package.json defines predev or postdev, those run automatically before and after npm run dev. More consequentially, preinstall, install, and postinstall scripts run automatically during npm install — before you've run a single command of your own.

This is a real supply chain attack vector. A malicious package can put arbitrary code in a postinstall script that executes the moment it's installed as a dependency. The command doesn't have to be npm run dev at all — simply installing the project's dependencies can trigger code execution. Several documented npm supply chain incidents have used exactly this mechanism to exfiltrate environment variables or credentials during install.

Two practical defenses:

  • Inspect install scripts in untrusted projects. Look at the dependencies' lifecycle scripts, or install with scripts disabled when you only need the files:
npm install --ignore-scripts

Note that some legitimate packages need install scripts to compile native modules, so this can break certain dependencies — use it deliberately, not as a blanket default.

  • Prefer npm ci in automation. In CI, npm ci installs exactly what the lockfile specifies, giving reproducible, tamper-evident installs.

Dev servers and their own exposure

Beyond install-time risk, the development server that npm run dev starts has its own considerations:

  • Don't bind dev servers to public interfaces. A dev server started with --host 0.0.0.0 is reachable from your network. Dev servers prioritize convenience over hardening and often expose source maps, unminified code, and verbose errors. Keep them on localhost unless you have a specific reason not to.
  • Dev dependencies still carry risk. Tools that only run in development are still real code with real dependency trees. A vulnerability in a build tool or its transitive dependencies runs on your developer machine, which often holds credentials and SSH keys. devDependencies are not exempt from scanning.
  • Environment files. Dev scripts frequently load .env files. Make sure those aren't committed and that the dev server doesn't serve them.

Keeping the whole thing trustworthy

The theme across all of this is that npm run dev is only as trustworthy as the package.json, the dependency tree behind it, and the lifecycle scripts they define. The command itself is fine; what it can pull in is the concern.

Practically, that means treating your package-lock.json as a security artifact, reviewing dependency additions the way you'd review code, and scanning the full tree — dev dependencies included — for known vulnerabilities and suspicious install scripts. Continuous software composition analysis does this automatically; a tool such as Safeguard can flag a newly-added dependency with a known CVE or an unexpected install script before it lands in your lockfile. For a deeper look at how this attack class works, see our writeup on the protobuf.js prototype pollution CVE.

FAQ

What does npm run dev actually do?

It runs the command defined under the dev key in your package.json scripts object — usually starting a local development server with hot reloading, such as vite, next dev, or nodemon. The exact behavior depends entirely on how that script is written in the project.

Why does npm run dev work but npm dev doesn't?

Because dev is a custom script, not one of npm's built-in commands. npm has shortcuts for a few names (start, test, stop, restart) that work without run, but every other script — including dev — must be invoked with npm run <name>.

Is npm run dev safe to run on an unfamiliar project?

It runs arbitrary commands from the project's package.json, so inspect the script first with npm run (which lists all scripts) or by reading package.json. Also be aware that npm install alone can run postinstall scripts before you run anything, which is a known supply chain risk. Use npm install --ignore-scripts for untrusted code when native builds aren't needed.

How do I see all available npm scripts?

Run npm run with no arguments. It prints every script defined in package.json along with the command each one executes, which is the quickest way to understand what a project's tooling does before running it.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.