Skip to main content
Deno 2.5 is here 🎉
Learn more

How Deno protects against npm exploits

Two major security breaches happened in npm this month: the @ctrl/tinycolor package (along with 40+ packages across multiple maintainers and 2+ million weekly downloads) was compromised likely via a hijacked postinstall script, and debug, chalk, among 18 other packages totaling billions of weekly downloads via a phishing email attack. These security exploits have happened and will continue to happen to the world’s largest open source registry due to Node/npm’s reckless approach to dependency management (your Node app and npm have unfettered access to everything — your filesystem, the internet, environment variables, etc. — when you install them).

However, there is hope. You can use Deno to run your Node apps (without any code change) to help mitigate these security vulnerabilities.

Node and npm’s unfettered access

Node.js and npm’s default security model is very permissive. When you run npm install or import a package in Node, any code in that package (even deeply nested depdencies) runs with full access to your system by default. This includes install scripts like postinstall hooks, which npm executes automatically. Someone even showed that running npm install on a compromised package could execute an npx command that exfiltrates your entire bash history to a remote server without any special privileges or consent needed.

This “run everything with no limits” approach means a single malicious dependency can, for examlpe, scan your filesystem for API keys and upload them to a remote server, as what happened in the tinycolor attack.

Secure by default

Deno, designed explicitly with security in mind, approaches permissions opposite of Node. By default, Deno executes code in a sandbox with no OS access. Unless you explicitly grant permission, a Deno program cannot:

  • read or write files to your file system
  • open network connections or send/receive data over the network
  • access environment variables
  • spawn subprocesses to run other programs

That means if you run or install some third-party code with Deno, it will block access to reading or writing to your local system, environment variables, or your network. Deno won’t let code escalate privileges at runtime either: the developer must deliberately opt-in. With Deno, you get a powerful safety net that lets you run untrusted or new code with much less fear of it doing harm.

Granting explicit access to your Deno program is simple via permission flags:

Permission it grants Flag Shorthand
Read access to the file system --allow-read -R
Write access to the file system --allow-write -W
Network access --allow-net -N
Access to environment variables --allow-env -E
Run subprocesses --allow-run none
All permissions (disable sandbox) --allow-all -A

You can even get more granular over the control you have over Deno’s access. For instance, you can give partial access to the file system:

deno --allow-read=/path/to/specific/file.txt your_script.ts

Or even allow access to only a single network endpoint:

deno --allow-net=api.stripe.com app.ts

And if juggling permission flags clutters your command line, you can define multiple permission sets in your deno.json that you can invoke with --permission-set (or -P). This is great if your permissions vary among running your app, running tests, deno compile, and so on.

We’re continuing to improve on our security features. For instance, we will soon ship a filter to only install npm packages of a certain age.

Opt into postinstall scripts with --allow-scripts

Deno can also be used as a package manager, with deno add, deno install, deno outdated and deno remove. However, unlike npm, Deno doesn’t automatically run postinstall scripts.

To permit specific packages to run their postinstall scripts, use the --allow-scripts flag:

deno install --allow-scripts=npm:sqlite3

This command will allow npm:sqlite3 to run its postinstall script, while blocking others. This setup gives you more control over which scripts, if any, can execute, protecting your system for potentially harmful or untrusted code.

Security through transparency

Deno can log every permission a program uses for auditing. By setting the environment variable DENO_AUDIT_PERMISSIONS to a file path, you get a detailed log of all permission checks and uses:

{
  "v": 1,
  "datetime": "2025-09-05T12:12:35Z",
  "permission": "env",
  "value": "FOO"
}
{
  "v": 1,
  "datetime": "2025-09-05T12:14:18Z",
  "permission": "read",
  "value": "data.csv"
}
{
  "v": 1,
  "datetime": "2025-09-05T12:14:26Z",
  "permission": "write",
  "value": "log.txt"
}

This means you could run a new module with auditing on and see exactly what it tried to do (a great way to catch unexpected behaviors). For even more observability, you can enable environment variable DENO_TRACE_PERMISSIONS=1 to include stack traces for where in the code the access was attempted.

A standard library for JavaScript

Another main criticism of npm is the proliferation of micro-libraries, which creates sprawling yet brittle dependency trees where a single library can compromise the entire project (see left-pad incident). You can reduce the surface area of vulnerability by minimizing dependencies: writing your own function or using a standard library.

Deno has been developing and maintaining the Standard Library for the past five years, which includes over 40 packages meant covering a wide variety of use cases, such as data manipulation, javascript functionalities, web-related logic, and general utilities.

Deno standard library modules

Learn more about the Deno standard library in our 3 minute YouTube video or see examples of each package in this tweet thread.

Using the standard library can minimize dependency bloat and improve security vulnerabilities in supply chain attacks.

JSR: a modern package registry

JSR modernizes the JavaScript package registry through offering first-class TypeScript support, cross-runtime and environment support, many other developer experience improvements, as well as improved security tools to help authors and users better safeguard against potential supply chain attack vectors.

During the module publishing process, authors will always be prompted to authorize their JSR account. This additional authorization layer is an added protection to ensure that the right author is publishing the module.

In addition, when a package is published via GitHub Actions, JSR automatically creates provenance statements for package users to understand the security and trustworthiness of a package. This added layer of verification helps users determine the safety of using packages from JSR.

JSR automatic provenance statement

Provenance statement and transparency log can be viewed on the bottom left of the package page on JSR.

Deno’s secure sandbox in the real world

Aside from our own Deno Deploy and Subhosting where we’re constantly executing untrusted code, there are many examples where Deno’s secure-by-default model makes it a great candidate for safe execution of code.

Since so much code now is LLM-generated, being able to execute said code safely has emerged as a top priority for agentic developers. LangChain Sandbox as well as Pydantic AI’s MCP server have both created solutions for running untrusted Python code, built with Pyodide and Deno. (here’s how you can do it yourself).

Not just Python either. LMStudio, a local LLM application, uses Deno to execute LLM-generated JavaScript and TypeScript in a secure sandboxed environment.

What’s next

No single tool eliminates supply-chain risk, but stronger defaults help. Deno’s model of explicit permissions and ongoing security features is one way to make running JavaScript a little safer.