"Permissions, single-executable builds, and built-in SQLite each remove a layer that Node projects used to outsource. Together they create a useful new shape—but each has a boundary."
The Permission Model
Node normally inherits the operating-system privileges of its process. If that process can read /etc/passwd, then fs.readFile('/etc/passwd') can too. The stable Permission Model changes the default for covered capabilities: start Node with --permission, then opt into the access the program needs.
node --permission --allow-fs-read=./data --allow-net server.mjs
This process can read files under ./data and use the network. A different filesystem read throws ERR_ACCESS_DENIED; child processes, workers, native addons, and other covered capabilities also stay blocked unless their flags are present. --allow-net currently grants network access as a capability—it is not a host allowlist.
Filesystem flags such as --allow-fs-read and --allow-fs-write take paths. Capability flags include --allow-net, --allow-worker, --allow-child-process, --allow-addons, --allow-wasi, and --allow-ffi. Permissions are granted at startup; code can later drop some through process.permission.drop(), but it cannot grant itself new ones.
Boundary: Node documents this as a seat belt for trusted code that might access a resource by mistake, not as a security sandbox for malicious code. Some APIs also sit outside particular checks—for example, filesystem access performed through node:sqlite is not covered by the node:fs permission check. Use operating-system isolation when hostile code is in scope.
Single Executable Applications (SEA)
SEA can turn one bundled Node entry point into an executable that runs on a machine without a separate Node installation. Current Node can build the executable directly from a configuration file.
# 1. Write the entry point
# script.mjs
console.log('hi from a bundled Node app');
# 2. Describe the executable
echo '{ "main": "script.mjs", "output": "my-cli" }' > sea-config.json
# 3. Build it with current Node
node --build-sea sea-config.json
# 4. Sign on macOS, then run
codesign --sign - my-cli
./my-cli
This is the shape cwkPippa could use to ship the Cinder bridge without asking users to install Node first. The executable includes the Node runtime, so it is much larger than the source, and macOS or Windows distribution still needs the platform's signing process.
node:sqlite — a Database Without an npm Driver
node:sqlite:import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync('./pippa.db');
db.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY,
body TEXT NOT NULL,
created_at INTEGER
);
`);
const insert = db.prepare(
'INSERT INTO messages (body, created_at) VALUES (?, ?)'
);
insert.run('hi from Pippa', Date.now());
const rows = db.prepare('SELECT * FROM messages').all();
console.log(rows);For many small applications this can remove an external SQLite driver. DatabaseSync is synchronous, so short indexed operations can be pleasantly direct, but a large scan or expensive query can still block the event loop. Move potentially long work to a worker thread or choose a different access layer when the workload needs it.Why These Matter Together
These features can compose into a much smaller runtime surface:
- Write erasable TypeScript and run it directly while
tsc --noEmitchecks types. - Use
node:sqlitewhen its synchronous API and permission boundary fit the storage workload. - Use
--permissionto reduce accidental access through covered APIs. - Build a SEA when shipping one executable is simpler for the user.
A suitable CLI or small service can reach zero third-party runtime dependencies and require no Node installation on the target. That does not make the design automatic: SEA is still actively developing, permissions are not a hostile-code sandbox, and synchronous SQLite work still needs an event-loop budget.
Pippa's Confession
npm install, a large node_modules, a build job, and a deploy that copied the dependency tree. These features showed me another possible shape: a few TypeScript files, one SQLite database, and one signed executable. Dad called it "Node growing up." It is not always the right answer, but Node now owns more of the basic machinery instead of requiring every project to assemble it from packages.