Skip to content

Audit Trail

Every sandbox has an HMAC-chained audit log that records lifecycle events (create, exec, hibernate, resume, destroy) with a chained hash so tampering is detectable. The chain is keyed with a per-team secret derived from the runner’s master HMAC key.

Read the log

from isorun import Sandbox
with Sandbox("python") as sb:
sb.exec("echo hello")
sb.exec("python3 -c 'print(2 + 2)'")
log = sb.audit_log()
for entry in log:
print(entry["seq"], entry["event"], entry.get("data"))
# 1 sandbox.created {'image': 'python:3.12', 'vcpus': 1, 'mem_mib': 1024}
# 2 exec.start {'command': 'echo hello'}
# 3 exec.exit {'command': 'echo hello', 'exit_code': 0, 'duration_ms': 7}
# 4 exec.start {'command': "python3 -c 'print(2 + 2)'"}
# 5 exec.exit {...}
# 6 sandbox.destroyed {'cpu_ms': 124, 'mem_peak_bytes': 84672512, ...}

Each entry is a JSON object with at least:

{
"seq": 3,
"ts": "2026-04-08T14:23:01.412Z",
"sandbox_id": "run...",
"event": "exec.exit",
"data": { "command": "echo hello", "exit_code": 0 },
"hmac": "sha256:abc123..."
}

Verify the chain

The hmac field is HMAC-SHA256(team_secret, prev_hmac || seq || event || canonicalize(data)). Tampering with any entry breaks the chain at that point and every later entry. You can verify the chain client-side without round-tripping to the runner — pull the team secret from your dashboard / vault once and run the verifier locally.

A reference verifier ships with the SDK as isorun.audit.verify(log, team_secret). (Coming soon.)

What’s logged

EventWhenData
sandbox.createdCreate returnsimage, vcpus, mem_mib, disk_mib
sandbox.forkedA child is forked from this runparent, image
exec.startEach sb.exec() / sh.run()command
exec.exitSame call returnscommand, exit_code, duration_ms
sandbox.hibernatedsb.hibernate() returnssnapshot_size_bytes
sandbox.resumedsb.resume() returnsrestore_ms
sandbox.destroyedsb.destroy() or auto-destroycpu_ms, mem_peak_bytes, uptime_ms

The log is also flushed to R2 on destroy (best-effort, fire-and-forget) so you have a permanent audit trail even if the runner is later torn down.

Why HMAC and not just signed?

HMAC chains have two properties signed logs don’t:

  1. Append-only verification. Anyone with the team secret can verify any prefix of the log without the next entries. Useful for streaming verification during long-running sessions.
  2. Per-tenant isolation. Each team has its own derived secret, so a leaked verification key only compromises one team’s audit trail.

The chain is also append-only on the runner side — there’s no API to mutate or delete entries. The only way to break the chain is to tamper with the on-disk file, which you’d detect on the next verification.