Shipping human takeover, and the RBAC gap we shipped it with
We shipped three things that belong together: an audit log of everything a sandbox did, a live feed of what it's doing right now, and a way for a human to reach in and take over a session directly. The first two are unambiguously good. The third came with a tradeoff we made deliberately and are disclosing here rather than letting anyone find out the hard way.
One audit table, several writers and readers
The audit log is a new exec_log_entries table and ExecLogEntry model in the control-plane, written through one shared _log_exec_entry helper wired into all seven existing sandbox routes. On top of that: a paginated GET .../log route and a GET .../watch Server-Sent Events route for a live feed.
Every operation writes to one table; two routes read it back
source: "human_takeover"exec_log_entriesExecLogEntry — control-plane audit tableGET .../logpaginated historyGET .../watchSSE live feedAuditSink.record_exec hook wired through bash_tool. GET .../log and GET .../watch are hosted-mode only — local docker-compose mode has no audit store of its own.Both routes have matching get_log/watch methods on the Python and JS SDKs, and boxkite log/boxkite watch CLI commands.
The takeover connection path
Takeover is the interesting one: a sidecar WS /ptyendpoint allocates a real interactive PTY into the sandbox's namespace, a control-plane WS /v1/sandboxes/{id}/takeover route proxies it, and our own dashboard now has a takeover terminal on top of that.
Human ⇄ control-plane proxy ⇄ sidecar PTY
WS .../takeovercontrol-plane proxy · auth checked before upgradeWS /ptysidecar · real PTY into sandbox namespacetakeover(session_id) method, so this is reachable from code, not only the dashboard.The tradeoff we shipped it with
That's a scoping decision, not a bug we missed — it's written down in SECURITY.md's "Known follow-ups" as a disclosed, accepted risk, and fine-grained RBAC on takeover is the tracked follow-up whenever there's time for it.
The mitigation we shipped in the same change as the feature itself: every operation during a takeover session — including every keystroke or command a human issues — is written to the same exec_log_entries audit table as everything else, tagged source: "human_takeover". So even without RBAC, what happened during any takeover session is always reconstructable afterward.
We'd rather ship this with the gap written down than wait for full RBAC before anyone gets an audit log or a live feed at all — but we wanted the tradeoff stated as plainly here as it is in SECURITY.md, not buried in a changelog line.