Example · boa-sandbox
Sandboxed JavaScript with capability grants (Boa)
boa · sandboxed JSriscv · RISC-V VM
The boa runner is the Boa JavaScript interpreter compiled to WebAssembly. A script evaluated with nano.script() gets a nano global whose powers are exactly what expose grants: file access (none / readonly / readwrite), run (BusyBox on the RISC-V VM), node, and a short list of web APIs. Anything not granted does not exist inside the sandbox.
This script lists and counts lines in a project directory, runs wc -l through the granted run capability (a BusyBox applet on the RISC-V VM — a cross-tier call), and shows that fetch is absent.
Run it
Run the sandboxed script
Evaluated by Boa with fs + run granted. Try calling fetch(...) — it is not defined.
javascript
// Runs in the Boa sandbox (nano.script). This engine was granted the `fs` and // `run` capabilities plus console — nothing else: no network, no DOM, no host // JavaScript objects. The value of the last expression (awaited if it is a // promise) comes back to the page as JSON. (async () => { const dir = "/work/project"; const files = nano.fs.list(dir).map((f) => f.name).sort(); console.log("files:", files.join(", ")); let total = 0; for (const name of files) { const lines = nano.fs.readText(dir + "/" + name).split("\n").filter(Boolean).length; total += lines; console.log(name.padEnd(12), lines, "lines"); } console.log("total:", total, "lines"); // `run` is a granted capability: a BusyBox applet on the RISC-V VM. const wc = await nano.run("wc -l " + dir + "/readme.md"); console.log("busybox wc -l:", wc.stdout.trim()); // Not granted: the global simply doesn't exist inside the sandbox. console.log("typeof fetch:", typeof fetch); return { files: files.length, total }; })();
Expected output
files: app.js, readme.md, util.js
app.js 2 lines
readme.md 3 lines
util.js 2 lines
total: 7 lines
busybox wc -l: 3 /work/project/readme.md
typeof fetch: undefined
→ {
"files": 3,
"total": 7
}How it runs
createNano({ scripting: { wasm: "/nano/boa.wasm" } })— boa.wasm loads on the first script.- The three project files are written to
/work/project/. nano.script(source, { expose: { fs: "readwrite", run: true, webapis: ["console", …] }, limits, timeoutMs })evaluates the script; console output streams to the pane and the final value comes back as JSON.
Source
From demos/boa-sandbox/.
script.js
javascript
// Runs in the Boa sandbox (nano.script). This engine was granted the `fs` and // `run` capabilities plus console — nothing else: no network, no DOM, no host // JavaScript objects. The value of the last expression (awaited if it is a // promise) comes back to the page as JSON. (async () => { const dir = "/work/project"; const files = nano.fs.list(dir).map((f) => f.name).sort(); console.log("files:", files.join(", ")); let total = 0; for (const name of files) { const lines = nano.fs.readText(dir + "/" + name).split("\n").filter(Boolean).length; total += lines; console.log(name.padEnd(12), lines, "lines"); } console.log("total:", total, "lines"); // `run` is a granted capability: a BusyBox applet on the RISC-V VM. const wc = await nano.run("wc -l " + dir + "/readme.md"); console.log("busybox wc -l:", wc.stdout.trim()); // Not granted: the global simply doesn't exist inside the sandbox. console.log("typeof fetch:", typeof fetch); return { files: files.length, total }; })();
project/app.js
javascript
const answer = 42; export const A = answer;
project/util.js
javascript
export const sum = (a, b) => a + b; export const PI = 3.14159;
project/readme.md
markdown
# demo
sandboxed scripting
over the VM's files