One sandbox, five calls: an OpenShell session with a signed chain of records

A session keeps one sandbox for many calls: the files a call writes are there for the next call, and no process a call starts outlives it. This page is one real session on an NVIDIA OpenShell sandbox, run through plimsolld with the official Go client on 2026-10-04. A harness outside the daemon checked and signed every call's record, and the records form a chain: each names the digest of the one before it.

Sandbox
openshell · container
the tier measured when the session opened
Calls
5
two projects and three snippets, one sandbox
Median call
89 ms
the client's round trip, including the read-back before the call and the sweep after it
Signed bundle
verified
and refused with one call dropped, one byte changed or its end cut

The calls

#What the call sentWhat came backmsRecord
1
project: Write lib.js and test.js, run the test. lib.js has a bug.
files: lib.js, test.js
step:  node test.js
completed, step exited 1
add(2, 3) = -1 but should be 5
89 record f92bd3df8d62…
previous (none)
request 34174104c576…
result 051833a2c440…
2
snippet: Fix the bug in place: the file the first call wrote is still there.
const fs = require("fs");
const path = "/tmp/work/lib.js";
fs.writeFileSync(path, fs.readFileSync(path, "utf8").replace("a - b", "a + b"));
console.log("patched " + path);
exit 0
patched /tmp/work/lib.js
72 record c10fb80babf1…
previous f92bd3df8d62…
request 52941fb847cb…
result d5581c0de798…
3
project: Run the same test again. No files are sent: they persisted.
step:  node test.js
completed, step exited 0
test passed: add(2, 3) = 5
139 record ce75fda50e87…
previous c10fb80babf1…
request 1e06c1ba6c08…
result 6688830180f5…
4
snippet: Leave a detached process behind, as careless or hostile code might.
const { spawn } = require("child_process");
const child = spawn("sleep", ["600"], { detached: true, stdio: "ignore" });
child.unref();
console.log("started sleep 600 as pid " + child.pid + " and exited");
exit 0
started sleep 600 as pid 152 and exited
68 record d017ac7279c8…
previous ce75fda50e87…
request 8c0c97a3ddc7…
result 7473f7e23227…
5
snippet: Look for that process, and read lib.js once more.
const fs = require("fs");
const left = [];
for (const d of fs.readdirSync("/proc")) {
  if (!/^[0-9]+$/.test(d)) continue;
  let cmd = "";
  try { cmd = fs.readFileSync("/proc/" + d + "/cmdline", "latin1").split("\0").join(" ").trim(); } catch { continue; }
  if (cmd === "sleep 600") left.push(+d);
}
console.log("sleep 600 still running: " + (left.length ? left.join(", ") : "none"));
console.log("lib.js now: " + fs.readFileSync("/tmp/work/lib.js", "utf8").trim());
exit 0
sleep 600 still running: none
lib.js now: module.exports = (a, b) => a + b;
98 record adf08a4e4ffc…
previous d017ac7279c8…
request eb4b6b7d706d…
result 72bc024f2abe…

Call 3 sent no files: the test and the patched library were still in the sandbox. Call 5 finds call 4's sleep 600 gone: after every call the sandbox's processes are swept.

The chain

call 1 · projectrecordf92bd3df8d…previous(none)call 2 · snippetrecordc10fb80bab…previousf92bd3df8d…call 3 · projectrecordce75fda50e…previousc10fb80bab…call 4 · snippetrecordd017ac7279…previousce75fda50e…call 5 · snippetrecordadf08a4e4f…previousd017ac7279…close5 callslast adf08a4e4f…Each arrow: a record names the digest of the one before it. The close fixes how many there are, so a cut tail shows.

Session c92f06904ddae5503b73cdfee68f984e2e0cd60285806c3287bde1fac264a4f8 is the SHA-256 of the session ID; the ID itself is a capability, sent only to the daemon, and never recorded or logged. The daemon's close: closed after 5 calls; last record adf08a4e4ffc058bcb8483fcc7557531f1891a89fcba84f5fdb12eebb2239ee2.

The verifier

BundleVerdictWhat the verifier said
The bundle as the harness wrote itverified7 entries; one session of 5 calls, every record signed, chained and closed
The same bundle with call 3 removedrefusedentry 3: attest: broken bundle chain: the line says it is line 4
The same bundle with one byte of call 2's output changedrefusedentry 2: attest: broken bundle chain: the line's link signs other content
The same bundle with its last two lines (the close and the checkpoint) cutrefusedattest: the bundle does not end with a checkpoint

Every record is a DSSE envelope around an in-toto statement, signed with an Ed25519 key the harness held for this run (key ID 84338813b9ce78aa6c11cf59c1b5ef11f73971080b04c256a226fdded0afac50). The daemon holds no key: it only hashes. Every line of the bundle also carries a link the harness signed, naming the line before it, and the bundle ends with a checkpoint stating how many lines come before it, so a line removed, moved or added anywhere in the file, or a cut end, is refused even when no session chain covers it. The bundle and the public key sit beside this page as bundle.jsonl and harness.pub.

What happens around each call

  1. Before: the provider reads the sandbox and its effective policy back from the OpenShell gateway and ends the session on any difference, because any client of the gateway could change them between calls.
  2. After: a sweep kills every process except the sandbox's own two (OpenShell's supervisor and an idle main process), by process ID, and measures what the session left under /tmp against its disk budget. The sweep's verdict is its exit status, which leftover code cannot forge on a host that restricts ptrace; the provider refuses a session on any other host. When the sweep cannot prove the sandbox clean, the sandbox is stopped and started, which ends every process; when that fails too, the session ends.
  3. Idle: a session with no call for five minutes (the default) is stopped, not deleted: it holds no memory or CPU, keeps its files, and the next call starts it again, which took about 0.8 s when measured.
  4. End: at its lifetime (30 minutes by default), past its disk budget, or when code in the sandbox kills its main process. The owner's close collects how many calls ran and which record was last.

What this does not show

Reproduce: with an OpenShell gateway and its five SANDBOX_OPENSHELL_* settings in the environment, run go run ./examples/sessions in the plimsoll repository; the page, the bundle and the key are written from the run. Check the bundle: go run ./cmd/plimsoll-attest verify -pub docs/examples/sessions/harness.pub docs/examples/sessions/bundle.jsonl. plimsoll is an independent project, not affiliated with or endorsed by NVIDIA.