Skip to main content
Shell lines like node script.mjs or js -e "..." need a JavaScript engine. Mirage calls that engine a runtime, and node and js are two names for the same command family. One runtime ships today:

What it is for

node/js runs small, self-contained scripts and pipe transforms: compute, JSON munging, regex text processing, code generation. It is a bare modern engine (ES2023 syntax, ES modules, JSON, Promise, top-level await), not Node: there is no require, no npm, no process, no fs, no fetch. Anything that needs those belongs in a sandboxed deployment, not the workspace runtime.
Three input forms, mirroring python3:
  • -e <code> evaluates the argument.
  • A file (node script.js) is read through the workspace before the run, so a mounted script executes.
  • stdin with no -e or file runs the piped text as the program.
scriptArgs holds the arguments after the code or script, and std.in.readAsString() reads piped stdin (the quickjs-ng std/os globals are exposed). These match the Python quickjs runtime, so a script behaves the same in both languages.

Modules

A .mjs file runs as an ES module automatically (top-level import/export/await); .js and -e run as a classic script. Pass -m/--module to force module mode for inline code:

Isolation

The engine runs under WebAssembly capability isolation: it sees only what the run passes it. Host files and the network are invisible. Workspace mounts are visible with no setup: mirage intercepts the sandbox’s filesystem calls and bridges them through the workspace dispatch, so std.open('/data/f.txt', 'r') reads the mount live — RAM, Redis, S3, or a virtual backend — and writes are immediately visible to every command, with the same cache, write modes, and per-session mount narrowing as cat. A read-only mount (or a session narrowed to read) fails the open, so std.open returns null instead of writing. This is capability isolation, not a resource sandbox: the sandboxed code cannot reach your files or the network, but its CPU and memory are bounded only by command_safeguards timeouts. For untrusted code or hard resource limits, run behind a sandboxed deployment. Each run gets its own epoch-interruption engine, so a command_safeguards timeout traps the run and reclaims the thread instead of leaking it.

Setup

The runtime needs the quickjs extra plus a WASI build of quickjs-ng (the qjs-wasi.wasm asset from a quickjs-ng release):
Point mirage at the directory holding qjs-wasi.wasm with the home option on the runtime entry (or the MIRAGE_QUICKJS_HOME environment variable):
The first run compiles qjs-wasi.wasm and caches the compilation as qjs-wasi.cwasm next to it; runs after that boot in milliseconds.

Resource limits

node/js is a command like any other: the same command_safeguards that guard cat or python3 guard it, enforced at the same central point. A run that exceeds timeout_seconds answers with exit 124, and max_bytes/max_lines cap its output. Firing the guard also cancels the run and reclaims the engine at the deadline.