Isorun Docs
Security

Audit Trail

Tamper-evident NDJSON log of every event in a sandbox's lifetime. Per-tenant.

Every sandbox has a tamper-evident audit log that records lifecycle, command, file-transfer, credential-proxy, and blocked-egress events with a chained HMAC, so any change to a past entry is detectable. The chain is keyed with a per-team secret.

Read the log

TypeScript
import { Isorun } from 'isorun'

const isorun = new Isorun()
const sandbox = await isorun.create({ image: 'python:3.12-slim' })

try {
  await sandbox.exec('echo hello')
  await sandbox.exec("python3 -c 'print(2 + 2)'")

  for (const entry of await sandbox.auditLog()) {
    console.log(entry.timestamp, entry.event, entry.data)
  }
  // 2026-05-26T…  pool.warm_popped  { image: 'python:3.12-slim', ... }
  // 2026-05-26T…  sandbox.created   { image: 'python:3.12-slim', vcpus: 1, ... }
  // 2026-05-26T…  exec.start        { command: 'echo hello' }
  // 2026-05-26T…  exec.exit         { command: 'echo hello', exit_code: 0, duration_ms: 12 }
  // 2026-05-26T…  exec.start        { command: "python3 -c 'print(2 + 2)'" }
  // 2026-05-26T…  exec.exit         { command: "python3 -c 'print(2 + 2)'", exit_code: 0, duration_ms: 41 }
} finally {
  await sandbox.destroy()
}

Each entry is a JSON object with at least:

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

Verify the chain

Each entry is chained to the previous one with a keyed hash, so tampering with any entry breaks the chain at that point and every later entry. You can verify the chain locally without round-tripping to the runner: pull your team secret from the dashboard once and run the verifier against the log.

What's logged

EventWhenData
pool.warm_createdA warm-pool VM is pre-booted (its log carries into your sandbox)image, vcpus, mem_mib
pool.warm_poppedA warm VM is assigned to youimage, vcpus, mem_mib, pop_ms, org_id
sandbox.createdCreate returnsimage, vcpus, mem_mib
sandbox.forkedA child is forked from this runparent, image
exec.startEach exec()command
exec.exitSame call returnscommand, exit_code, duration_ms
file.uploadA file is written into the sandboxpath, size
file.downloadA file is read out of the sandboxpath, size
credential.proxiedA proxied credentialed request is madeservice, method, path, status (never the credential)
network.blockedAn egress connection is denied by policydomain or ip + port, reason
sandbox.destroyeddestroy() or auto-destroycpu_ms, mem_peak_bytes, uptime_ms

Command output (stdout/stderr) is never recorded, only the command line, its exit code, and duration. File events record path and size, not contents; credential-proxy events never include the credential.

The log is flushed to object storage (R2) on destroy (best-effort) so you have a permanent audit trail even after the runner is torn down.

Guarantees

  1. Append-only verification. Anyone with the team secret can verify any prefix of the log without needing the later entries, useful for streaming verification during long-running sessions.
  2. Per-tenant isolation. Each team has its own secret, so a leaked verification key only affects that one team's audit trail.

There is no API to mutate or delete entries. The only way to break the chain is to tamper with the stored file, which you would detect on the next verification.

The flush to object storage on destroy is best-effort. Read the log before you destroy a sandbox if you need a guaranteed copy in hand.

Next steps

On this page