SDK

Scripting

Run untrusted JavaScript in a sandboxed Boa engine with capability-scoped access to the VM. A fresh engine has zero ambient authority — you grant exactly what a script may touch.

One-shot vs long-lived

javascript
// one-shot: create, eval, dispose
const r = await nano.script("({ sum: [1,2,3].reduce((a,b)=>a+b,0) })", {
  expose: {},
});

// long-lived engine you drive and dispose yourself
const engine = await nano.scripting({ expose: { fs: "readonly", run: true } });

script() creates an engine, evaluates the source, and disposes it. scripting() returns a long-lived ScriptEngine. Both require scripting.wasm (boa.wasm) in the Nano config.

The capability model

expose is the only security boundary — nothing is granted by default.

Grant
Effect
fs: "none" | "readonly" | "readwrite"
access to nano.fs (default none)
run: boolean
nano.run / exec / sh (default false)
node: boolean
nano.node (default false)
webapis: [...]
"console" | "encoding" | "url" | "timers" (default ["console"])

Host bindings

registerFunction(name, fn) exposes a host function (sync or async) to scripts; defineGlobal(name, value) injects a value; env is mirrored as <global>.env.

javascript
engine.defineGlobal("CONFIG", { timeout: 30 });
engine.registerFunction("fetchRow", async (id) => ({ id, name: `row-${id}` }));
const out = await engine.eval(`(async () => (await fetchRow(7)).name)()`);
engine.dispose();

Limits

  • limits.loopIterations / limits.recursion — cap runaway scripts.
  • timeoutMs — host-side watchdog.
  • syncOnly — skip the async job pump.
  • onStdout / onStderr — capture script output.
ScriptError is thrown on evaluation errors.