Node 26 turns the permission model on by default

Node's process-level permission system becomes opt-out rather than opt-in, meaning scripts must declare filesystem and network access. Expect a difficult few months for build tooling.

Younes Bekrar10 min read
ShareXLinkedInFacebook
Node 26 turns the permission model on by default

Node 26 entered its release candidate phase on Thursday with the permission model enabled by default, four years after Deno demonstrated the idea and three years after Node shipped an experimental implementation behind a flag. A Node process started without explicit grants can no longer read outside its working directory, write anywhere, open network connections to arbitrary hosts, or spawn child processes. Existing behavior is available through a single flag, and the release notes are direct that most people will need it initially. The technical steering committee approved the change in May by a margin of one vote, with dissenters arguing the ecosystem is not ready. They are probably right and the change is probably still correct.

What the defaults actually are

A process gets read access to its own directory tree and to the Node installation, write access to the operating system temporary directory, and nothing else. Network access requires an explicit allow list of hosts or a blanket grant. Child process spawning is denied. Native addon loading is denied, because an addon can bypass every check by calling system functions directly. Environment variable reading is permitted, which the security working group debated and allowed on the grounds that blocking it breaks essentially everything for marginal benefit.

example.ts
typescript
export async function handler(request: Request): Promise<Response> {  const started = Date.now();  const upstream = await fetch(request);  const headers = new Headers(upstream.headers);  headers.set("x-skarvonix-ms", String(Date.now() - started));  return new Response(upstream.body, {    status: upstream.status,    headers,  });}

Grants come from command line flags or from a permissions block in package.json, which is the ergonomic path most projects will use. The package.json form is declarative and reviewable, which means a dependency requesting broad access is visible in a diff. Permissions are process-wide rather than per-module, so a granted permission is available to every dependency in the process. That is a significant limitation and the working group is explicit that per-module scoping is a future project with unsolved design questions.

What breaks

Build tooling breaks first and worst. Bundlers read node_modules, write to a dist directory, and often spawn subprocesses for transpilation or minification. Test runners read fixtures from anywhere and write coverage output. Package installation scripts do essentially arbitrary things. Node's compatibility testing found that 84 percent of the top 200 build tools fail under default permissions without configuration, which sounds alarming and mostly reflects that these tools legitimately need file access nobody had previously bothered to declare.

The migration path the working group recommends is to run with permissions enabled and add grants as failures appear, which is tedious and produces a correct declaration at the end. Several tools have shipped recommended permission blocks. Vite, esbuild, and Vitest all publish them. The tools that will lag are the ones with a single maintainer and the ones abandoned years ago that half the ecosystem still depends on, which is the familiar shape of every JavaScript ecosystem migration.

The story is rarely the launch. It is what breaks, what ships, and who owns the mess at 2 a.m.
Younes Bekrar

The security value, honestly assessed

The attack this defends against is a malicious or compromised dependency doing something outside its stated purpose: reading SSH keys, exfiltrating environment secrets, or installing a persistent backdoor. That attack has happened repeatedly, most memorably with event-stream in 2018 and the ua-parser-js compromise in 2021, and it will happen again because npm's structure makes it easy.

Process-wide permissions blunt but do not stop it. If your application legitimately needs network access, a malicious dependency inside that process has network access, and exfiltration is trivial. The defense is meaningful for tooling that should not need network at all, which is where the most damaging supply chain attacks have landed. A linter with no network grant cannot send your environment variables anywhere, and that is worth having even though it is not a complete answer.

How this compares to the alternatives

Deno's model is finer-grained and enforced at more points, including a per-import-specifier scope that Node does not have. Bun has no permission model at all. Container isolation, which many teams already use for CI, provides stronger boundaries at a coarser granularity and does nothing for a developer running a build on a laptop, which is where credentials actually live.

The realistic security posture combines them: permissions to constrain what a process can do, containers to constrain what a compromise reaches, and dependency review to reduce how often you run untrusted code at all. Node adopting permissions closes the gap that made Deno's pitch compelling, which is presumably part of the motivation, and it does so with a design that is less complete and applies to vastly more code.

Timeline and practical advice

Node 26 becomes the active long-term support line in October. Teams should test against the release candidate now rather than in October, because the work is unglamorous and touches CI configuration, and discovering it during an upgrade window is worse. Running the test suite with permissions enabled produces a list of what your build actually accesses, which is useful information regardless of whether you adopt.

For library authors the ask is to publish a recommended permissions block and to stop doing surprising things at install time. Postinstall scripts that download binaries, which is how several popular packages work, are incompatible with a restrictive default and are also the mechanism behind a large share of npm supply chain incidents. The permission model makes that pattern visible, which may finally create enough pressure to retire it.


Skarvonix will keep following this beat with reporting grounded in how systems behave outside the launch keynote.

  • Open Source
  • TypeScript

Keep reading