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.
--allow-scripts
Opt into postinstall scripts with 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.
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.
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.