Allow opting out of `exec.log` capture (per sandbox and/or per exec) — interactive sessions are currently written to disk chunk by chunk
Summary
Since #650, every exec session's stdout/stderr is appended to <sandbox>/logs/exec.log, including interactive tty: true sessions. There is no way to turn this off. For workloads that stream a lot of interactive traffic through exec, that puts a JSON-encode and a file write on the hot path of every output chunk. I'd like to request an opt-out. I'd be happy to contribute the PR if you agree on the shape.
Context: our use case
We run training labs on microsandbox, with hundreds of concurrent sandboxes, and each player gets an interactive terminal through an exec session (tty: true). Our design rule for the lab data path is memory, network and IPC only: no disk I/O per interaction. Everything on our side of the relay respects that.
With capture-all, each keystroke echo and each chunk of output (a player running cat on a large file, find /, a loop printing to the terminal) becomes:
- a CBOR decode of the frame,
- a
serde_jsonencode, where terminal escape sequences inflate badly (ESCbecomes), - a
write_allonexec.log,
all done inline in ring_reader_task before the frame is routed to its client. So one noisy session also adds latency to every other session on the same sandbox. The 10 MiB × 3 rotation bounds disk usage, but not write volume: under load the file is just rewritten continuously.
A second, smaller concern: interactive transcripts end up persisted on the host, including anything a root shell prints. In our case that can include challenge secrets. Some deployments would rather not keep that on disk.
Reproduction (msb 0.6.18, macOS arm64)
- Create a sandbox whose detached startup command is a long-running process (session
id1). - Open an interactive exec session with
tty: trueand typeecho secret. tail -f ~/.microsandbox/sandboxes/<name>/logs/exec.log:
{"t":"2026-09-12T09:14:08.071Z","s":"output","d":"e","id":3}
{"t":"2026-09-12T09:14:08.173Z","s":"output","d":"c","id":3}
{"t":"2026-09-12T09:14:08.215Z","s":"output","d":"h","id":3}
{"t":"2026-09-12T09:14:08.428Z","s":"output","d":"o","id":3}
{"t":"2026-09-12T09:14:08.590Z","s":"output","d":" ","id":3}
{"t":"2026-09-12T09:14:09.057Z","s":"output","d":"s","id":3}
{"t":"2026-09-12T09:14:09.357Z","s":"output","d":"e","id":3}
{"t":"2026-09-12T09:14:09.446Z","s":"output","d":"c","id":3}
{"t":"2026-09-12T09:14:09.784Z","s":"output","d":"r","id":3}
{"t":"2026-09-12T09:14:11.398Z","s":"output","d":"e","id":3}
{"t":"2026-09-12T09:14:11.676Z","s":"output","d":"t","id":3}Each echoed keystroke is its own JSON line and its own write, and full output (prompts, command results) follows the same path.
Where it happens (v0.6.18, fa3e439)
- Every
ExecRequestis registered, with no filter:crates/runtime/lib/relay.rs#L1001-L1011 - Every registered session's stdout/stderr is tapped whenever a writer is attached:
crates/runtime/lib/relay.rs#L844-L850 - The writer is always opened at boot; there's no config switch:
crates/runtime/lib/vm.rs#L660-L663
main as of 2026-09-11 (74b8db2f) is the same.
Docs still describe the old primary-session model
#650 says the capture-all model is intentional ("the earlier first-session-wins design is gone"). Several doc comments still say only the primary session is captured, which is how we first misread the behaviour:
exec_log.rsmodule doc: "Only the primary session — the first exec session opened against the sandbox per lifetime — feeds this file" (L12-L14)relay.rsdoc comments at L107, L244, L677 and L771-L774. The last one mentions aprimary_session_idCAS that doesn't exist in the code.vm.rsL660
Proposal
Any one of these would solve it for us. They're listed from the smallest change to the most flexible:
- Sandbox-level switch, e.g.
SandboxBuilder::exec_log(false)/msb run --no-exec-log. The runtime then skipsLogWriter::open, and, as the existing comment at relay.rs L845-847 notes, the CBOR decode is skipped too when there's no writer. - Per-exec opt-out, e.g. a
capture: boolon the exec options /ExecRequest, checked when the session is registered. This keeps the default workload's output inexec.log(useful formsb logs) while interactive sessions stay off disk. - Default: don't capture
tty: truesessions, or make that the behaviour of a switch. This one is debatable, since pty capture was deliberately added (LogSource::Output), so we'd understand if you prefer 1 or 2.
Independently of the above, the stale doc comments should be updated to describe capture-all.
Thanks for microsandbox! The rest of the logging design (rotation-aware readers, typed sources, boot-error.json) has been very pleasant to build on.
Source: superradcompany/microsandbox