Skip to main content

Isolation, Sandboxing, and Compiled Binaries — Architecture Deck

Author: Alex Dankov (@Dankovk) Source: Notion — Isolation, sandboxing, binaries (2026-05-20) Status: Authoritative architectural reference for Human.work agent runtime isolation. Companion docs:

This deck defines the layered isolation model for the Human.work agent runtime: compiled runner + capability shell + no ambient secrets + OS/container sandbox + control-plane policy. Each layer is load-bearing; none alone is the security boundary.

Cross-ref: ADR-020 — Isolation Classification is the authoritative resource-class isolation matrix; #1132 lands the database-layer enforcement (RLS CHECK constraints on Conversation / Message tables) that complements the runtime/sandbox layers below. DB-layer CHECK + runtime sandbox + capability shell are independent layers of the same isolation contract — defence in depth.

Open issues anchored on this deck: #332 (compiled per-org policy enforcement), #362 (per-Org sandboxed runtime MVP), #401 (per-(Org × Specialist) runtimes v2), #237 (cross-Org isolation eval), #251 (per-client-agent auth secret), #625 (R2 client-data isolation), #678 (Google Workspace lifecycle & per-Specialist scoped OAuth — anchored on the "Environment policy" and "Control-plane credential proxy" sections).

Implementation status (last updated 2026-05-28):

  • Layer 1 — Control plane: ✅ landed (PR #696) plus active manifest/allowed executable enforcement in this branch.
  • Layer 2 — Compiled runner: 🟡 partial — runner_manifests.allowed_executables schema, build/sign/store workflow, runner_builds registry, and Python pull scaffold are in this branch; production registration/signature cutover still needs CI secrets and merge.
  • Layer 3 — Capability shell: 🟡 partial — typed gateway landed (#696); Zig capability shell and Python adapter are in this branch; full handler parity and production image publication still need review.
  • Layer 4 — OS/container sandbox: 🟡 scaffolded — ADR-008, seccomp profile, K8s/Firecracker scaffolds, and nightly deny-battery are in this branch; production substrate remains blocked on AlexD/Paul/Eusden cosign.
  • Layer 5 — Inspector: ✅ landed (PR #696).

Purpose

This document defines the architecture for compiled runtime binaries, capability-based execution, secrets minimization, and sandboxing in Human.work. The goal is to let agentic runtimes perform useful work without granting them ambient authority over the host, filesystem, network, credentials, or production systems.

The key idea

- compiled runner 
+ capability shell
+ no ambient secrets
+ OS/container sandbox
+ control-plane policy

A compiled runner is not a complete security boundary by itself. A sandbox is not a complete product architecture by itself. The useful design is layered.

Core thesis

Human.work should not expose a normal shell to an agent runtime. A normal shell is too expressive:

  • shell expansion;
  • pipes;
  • redirects;
  • arbitrary subprocesses;
  • easy secret dumping;
  • arbitrary network commands;
  • filesystem traversal;
  • unexpected inherited environment;
  • difficult audit. Instead, Human.work should expose a small native capability runtime that accepts structured operations and enforces policy at every boundary.
model/runtime
→ structured operation
→ control-plane policy
→ compiled runner manifest check
→ Zig capability shell
→ OS/container sandbox
→ event + artifact + audit

Design goals

Reduce execution surface

The runtime should not include every possible tool, library, script, or source file. It should only include what is necessary for the allowed workload.

Remove ambient secrets

The runtime process and its children should not receive broad secrets through environment variables or files.

Make authority explicit

Every operation should carry a clear capability:

  • what action;
  • what resource;
  • what run;
  • what timeout;
  • what network mode;
  • what filesystem scope;
  • what approval/policy decision.

Make execution observable

Every operation should emit events that can be shown in Ops/Expert surfaces.

Make enforcement layered

The architecture should not rely on one mechanism. It should combine:

  • control-plane authorization;
  • runner manifest validation;
  • capability shell checks;
  • OS/container restrictions;
  • credential isolation;
  • event/audit recording.

Non-goals

This design is not trying to:

  • make arbitrary shell access safe;
  • run untrusted arbitrary code with perfect containment;
  • replace server-side org/data scoping;
  • replace approval workflows;
  • place secrets inside compiled binaries;
  • let client-facing Specialist workflows mutate files or systems by default;
  • build a general-purpose PaaS inside Human.work.

Architecture overview

+-------------------------------------------------------------+
| Human.work Control Plane |
| |
| - run creation |
| - policy decisions |
| - approval gates |
| - credential proxy |
| - tool gateway |
| - event ledger |
| - artifact records |
+-------------------------------+-----------------------------+
|
| signed/scoped capability
v
+-------------------------------------------------------------+
| Compiled Runner |
| |
| - runner identity |
| - policy hash |
| - manifest hash |
| - allowed operation manifest |
| - control-plane verifier |
| - no embedded vendor secrets |
+-------------------------------+-----------------------------+
|
| structured operation JSON
v
+-------------------------------------------------------------+
| Zig Capability Shell |
| |
| - operation schema validation |
| - path allowlists |
| - argv-only process launch |
| - env scrubbing |
| - output/time caps |
| - process cleanup |
| - event emission |
+-------------------------------+-----------------------------+
|
| constrained process/file ops
v
+-------------------------------------------------------------+
| OS / Container Sandbox |
| |
| - read-only root filesystem |
| - bind mounts / tmpfs |
| - seccomp / namespaces / cgroups |
| - dropped capabilities |
| - network isolation / egress proxy |
+-------------------------------------------------------------+

Compiled runner

Definition

A compiled runner is a runtime artifact built for a specific execution profile. It contains the minimum code and manifest needed to perform allowed operations. It should be identifiable by a hash and version. Example identity:

humanwork-runner-{environment}-{policyHash}-{manifestHash}-{buildId}

The exact naming can change, but the important properties are:

  • environment-aware;
  • policy-bound;
  • manifest-bound;
  • immutable after build;
  • auditable by hash.

Why compile or bundle

A compiled or bundled runner is useful because it:

  • reduces shipped code;
  • reduces accidental tool exposure;
  • excludes source tree and dev tooling;
  • makes provenance explicit;
  • allows control-plane allowlisting by hash;
  • makes policy/manifest tampering harder;
  • gives incident response a concrete artifact to reason about.

What compilation does not solve

Compilation does not automatically:

  • prevent syscalls;
  • prevent arbitrary network egress;
  • protect secrets passed in environment variables;
  • enforce database scoping;
  • stop a child process from misbehaving;
  • replace an OS sandbox;
  • replace control-plane authorization. Compiled runner is one layer, not the whole boundary.

What to include

Compiled runner can include:

  • runner version;
  • environment;
  • allowed operation manifest;
  • allowed tool manifest;
  • policy hash;
  • prompt/spec bundle hash;
  • event schema version;
  • control-plane public key or verifier;
  • build metadata;
  • capability protocol implementation;
  • minimal operation handlers.

What not to include

Compiled runner must not include:

  • Nango secret keys;
  • Jumio API keys;
  • OAuth tokens;
  • database credentials;
  • JWT signing secrets;
  • master encryption keys;
  • Railway/API tokens;
  • client credentials;
  • long-lived credential material.

Runner manifest

A runner manifest describes what the runner is allowed to do. Example:

{
"runnerVersion": "2026.05.19.1",
"environment": "production",
"policyHash": "sha256:policy...",
"manifestHash": "sha256:manifest...",
"promptBundleHash": "sha256:prompt...",
"eventSchemaVersion": "v1",
"allowedTools": ["jumio_lookup", "quickbooks_query"],
"allowedOps": ["artifact_read", "artifact_write", "artifact_list", "exec_argv"],
"sandboxModes": ["read-only", "workspace-write", "network-proxy-only"],
"networkPolicy": "proxy-only",
"maxRuntimeMs": 300000,
"maxOutputBytes": 1048576,
"createdAt": "2026-05-19T00:00:00Z",
"expiresAt": "2026-06-19T00:00:00Z"
}

The manifest should be hashable and optionally signed.

Runner registration

On startup, the runner reports:

  • runner hash;
  • manifest hash;
  • policy hash;
  • version;
  • environment;
  • supported operations;
  • sandbox mode;
  • boot timestamp. The control plane should reject runners when:
  • runner hash is unknown;
  • manifest hash is stale;
  • policy hash is stale;
  • environment does not match;
  • manifest expired;
  • supported operations exceed manifest;
  • signature verification fails.

Manifest rotation and expiry lifecycle

The expiresAt field is enforced at the registration boundary, not mid-run. The lifecycle has three windows:

1. Pre-expiry refresh window — runners must pre-fetch a refreshed manifest before expiresAt. Default refresh window is min(expiresAt - 24h, expiresAt - 25% of manifest lifetime). A successful refresh swaps the in-memory manifest at the next op boundary and emits runner.manifest.refreshed. If refresh fails, the runner keeps using the current manifest and re-tries with exponential backoff; the control plane surfaces a runner.manifest.refresh_failed event after the first failure for ops visibility.

2. In-flight run grace period — runs that started while the manifest was valid finish on that manifest. The control plane does not kill mid-run because expiresAt ticked over. The bound is min(maxRuntimeMs, expiresAt + grace) where grace defaults to maxRuntimeMs (i.e. the longest a run could legitimately keep going). After expiresAt + grace, in-flight runs are force-failed with reason manifest_expired_with_grace_exceeded and the run lands in the dead-letter queue.

3. New-run gate — once now() >= expiresAt and no refreshed manifest has been registered, the control plane rejects new run claims with runner.rejected { reason: 'manifest_expired' }. Existing runs continue under window (2); the runner cannot pick up new work.

Key invariants:

  • A run that starts ≤ 1 second before expiresAt is still a valid run and gets the full grace window — the registration check happens once, at run-claim time, not continuously.
  • Manifest refresh is the runner's responsibility, not the control plane's push. The control plane only validates what the runner presents.
  • Emergency revocation is not expiry-based — see "Runner registration" rejection conditions and the manifest-signing key rotation policy referenced from RIG_HARD_CUT_RUNTIME_PORT_PLAN.md (implementation-lock decision #4). Revocation must take effect immediately; expiry is a steady-state hygiene mechanism.

Runner provenance events

Every run should emit:

  • runner.selected
  • runner.started
  • runner.policy.loaded
  • runner.manifest.loaded
  • runner.manifest.refreshed (on successful pre-expiry rotation)
  • runner.manifest.refresh_failed (when refresh fails — for ops visibility, not run termination)
  • runner.rejected if validation fails These events make it possible to reconstruct which runtime artifact executed which work.

Capability shell

Definition

The capability shell is a native runtime interface that performs structured operations. It is not a general shell. A good implementation language is Zig because it can produce a small native binary with explicit control over process spawning, filesystem operations, and runtime dependencies. The choice of Zig is useful, but not magical. The security property comes from a small operation surface and layered enforcement.

Protocol

The shell accepts structured JSON operations over stdin, socket, pipe, or a local RPC boundary. Each operation should include:

  • operation type;
  • run id;
  • correlation id;
  • arguments;
  • timeout;
  • output cap;
  • network mode;
  • working directory;
  • capability token or authorization reference. Example envelope:
{
"id": "op_123",
"runId": "run_456",
"correlationId": "corr_789",
"op": "artifact_read",
"args": {
"path": "inputs/customer.csv",
"maxBytes": 1048576
},
"policyDecisionRef": "pol_abc",
"createdAt": "2026-05-19T00:00:00Z"
}

Deny by default

Unknown operations fail. Missing fields fail. Invalid paths fail. Unapproved risky operations fail. Denied operations should emit structured output and a runtime event:

{
"id": "op_123",
"status": "denied",
"reason": "path_outside_workspace",
"eventType": "sandbox.exec.denied"
}

Supported operations

artifact_read

Reads a file/artifact from an allowed path.

{
"op": "artifact_read",
"args": {
"path": "inputs/customer.csv",
"maxBytes": 1048576
}
}

Enforcement:

  • path normalization;
  • no .. traversal;
  • symlink resolution;
  • allowed root check;
  • max byte cap;
  • redaction/access policy;
  • event emission.

artifact_write

Writes output to an allowed artifact path.

{
"op": "artifact_write",
"args": {
"path": "outputs/result.json",
"contentRef": "blob_ref_123",
"maxBytes": 1048576
}
}

Enforcement:

  • allowed output root;
  • max byte cap;
  • content hash;
  • atomic write;
  • artifact record creation;
  • event emission.

artifact_list

Lists files under an allowed artifact path.

{
"op": "artifact_list",
"args": {
"path": "inputs/"
}
}

Enforcement:

  • allowed root;
  • no hidden host paths;
  • max listing size;
  • event emission.

exec_argv

Runs an approved executable with argv array only.

{
"op": "exec_argv",
"args": {
"argv": ["python", "scripts/normalize.py", "inputs/customer.csv"],
"cwd": "/workspace",
"network": "off",
"timeoutMs": 30000,
"maxOutputBytes": 262144
}
}

Enforcement:

  • no shell string;
  • argv array only;
  • executable allowlist;
  • working directory allowlist;
  • env scrub;
  • timeout;
  • output cap;
  • process tree cleanup;
  • network mode check;
  • event emission.

http_request

Direct arbitrary HTTP should not be available by default. Prefer control-plane proxy.

{
"op": "http_request",
"args": {
"profile": "control-plane-tool-proxy",
"requestRef": "req_123"
}
}

Enforcement:

  • no arbitrary URL unless manifest allows;
  • allowlisted domains/profiles;
  • no raw secrets;
  • response redaction;
  • event emission.

browser_step

Browser operations should be treated as high-risk and artifact-producing.

{
"op": "browser_step",
"args": {
"sessionId": "browser_123",
"action": "click",
"selector": "button[type=submit]",
"requiresApproval": true
}
}

Enforcement:

  • domain allowlist;
  • session isolation;
  • screenshot/trace artifacts;
  • approval before submit/write actions;
  • no raw credential exposure;
  • event emission.

Why no raw shell

Raw shell strings are too hard to constrain. Dangerous properties:

  • command substitution;
  • globbing;
  • pipes;
  • redirects;
  • env access;
  • implicit path lookup;
  • shell builtins;
  • unexpected quoting;
  • arbitrary network tools;
  • arbitrary filesystem traversal. Structured operations are less convenient but much safer. The runtime should never accept:
{ "cmd": "..." }

It should accept typed operations only.

Environment policy

Problem with environment variables

Environment variables are ambient authority. They leak easily through:

  • env;
  • /proc/self/environ;
  • crash dumps;
  • child processes;
  • logs;
  • debugging output;
  • process listings on some systems. If secrets are placed in env, any allowed subprocess can potentially dump them.

Default environment

The capability shell should launch child processes with a scrubbed environment. Example minimal env:

PATH=/runtime/bin
HOME=/runtime/home
TMPDIR=/workspace/tmp
RUN_ID=run_...

Do not include vendor or platform secrets.

Secret access pattern

Preferred pattern:

operation requests credentialed action
→ control plane validates policy/approval
→ control plane performs credentialed call
→ redacted result returned

Fallback pattern:

operation requests short-lived capability
→ control plane issues scoped token
→ token passed over fd/stdin/memory
→ token expires after one operation

Avoid:

  • broad env secrets;
  • long-lived local token files;
  • secrets in manifest;
  • secrets in binary;
  • secrets in model context;
  • secrets in logs/events/artifacts.

Control-plane credential proxy

The credential proxy is the preferred way to use vendor secrets. For example, instead of giving the runner a Nango secret key:

runner requests quickbooks_query action
→ API validates run, policy, approval
→ API uses NANGO_SECRET_KEY internally
→ API calls Nango
→ API redacts/normalizes result
→ runner receives safe output

Instead of giving the runner a Jumio API key:

runner requests jumio_lookup
→ API validates org, policy, approval
→ API loads encrypted credential
→ API signs Jumio request
→ API returns masked result

This keeps secrets outside the execution runtime.

Filesystem isolation

Allowed roots

The runtime should only see or use explicit roots:

  • /workspace/input
  • /workspace/output
  • /workspace/tmp
  • /workspace/logs
  • /runtime/bin
  • /runtime/state if needed Everything else should be inaccessible or read-only.

Path handling

Every path operation must:

  • normalize path;
  • resolve symlinks;
  • reject traversal;
  • reject absolute paths unless explicitly allowed;
  • verify final path is under an allowed root;
  • enforce read/write mode.

Write policy

Writes should be limited to:

  • output artifact directory;
  • temp directory;
  • logs directory. No writes to:
  • source tree;
  • config directories;
  • credential directories;
  • other runtime sessions;
  • root filesystem.

Process isolation

Process launch

Only exec_argv should launch child processes. Rules:

  • executable allowlist;
  • argv array only;
  • no shell;
  • fixed cwd under workspace;
  • scrubbed env;
  • timeout;
  • stdout/stderr cap;
  • kill entire process group/tree on timeout;
  • record exit code and duration.

Output control

Output must be capped to avoid:

  • memory blowup;
  • log flooding;
  • accidental PII leaks;
  • prompt/context pollution. Large outputs should become artifacts, not inline model context.

Network isolation

Default network mode

Default should be off or proxy-only. Modes:

  • off: no network.
  • proxy-only: can reach control-plane proxy only.
  • allowlist: can reach explicitly listed domains.
  • full: development only, explicit, audited.

Why proxy-only is preferred

Proxy-only allows the control plane to enforce:

  • credential access;
  • domain allowlist;
  • request signing;
  • rate limits;
  • audit;
  • redaction;
  • approval gates. Direct arbitrary egress is hard to reason about and easy to abuse.

OS/container sandbox

Why it is required

The capability shell can prevent many unsafe operations by construction. But once a child process runs, OS-level enforcement is needed. A child process can attempt syscalls directly. The shell cannot stop those syscalls unless it is wrapped in an enforcement mechanism.

Linux controls

Preferred Linux controls:

  • namespaces;
  • chroot or pivot_root;
  • read-only root filesystem;
  • bind mounts for allowed dirs only;
  • tmpfs for temp;
  • seccomp-bpf;
  • cgroups;
  • no-new-privileges;
  • dropped Linux capabilities;
  • network namespace;
  • egress proxy;
  • optional Landlock.

Container controls

If using containers:

  • read-only rootfs;
  • no privileged containers;
  • drop all capabilities by default;
  • explicit volume mounts;
  • memory limits;
  • CPU limits;
  • PID limits;
  • network policy;
  • no host mounts;
  • no Docker socket;
  • no service account token unless needed.

Sandbox modes

Useful modes:

ModeMeaning
read-onlyRuntime can read approved inputs and write only logs/events.
workspace-writeRuntime can write workspace/output/tmp only.
artifact-writeWrites only through artifact operation path.
network-offNo network access.
network-proxy-onlyOnly control-plane proxy reachable.
danger-full-accessDev-only escape hatch, explicit and audited.
danger-full-access must never be silent.

Event and audit model

Every operation should emit events. Example events:

  • runner.started
  • runner.rejected
  • runner.manifest.loaded
  • capability.requested
  • capability.granted
  • capability.denied
  • sandbox.exec.started
  • sandbox.exec.denied
  • sandbox.exec.finished
  • artifact.created
  • process.timeout
  • network.denied
  • path.denied Event payloads should include:
  • run id;
  • operation id;
  • correlation id;
  • runner hash;
  • policy hash;
  • manifest hash;
  • operation type;
  • decision;
  • duration;
  • exit code;
  • output artifact refs;
  • denial reason. Event payloads should not include:
  • secrets;
  • raw tokens;
  • unredacted PII by default;
  • huge stdout/stderr blobs.

Artifact model

Large operation inputs/outputs should be artifacts. Artifact examples:

  • input file;
  • output file;
  • stdout excerpt;
  • stderr excerpt;
  • full log;
  • screenshot;
  • browser trace;
  • validation report;
  • sandbox denial report. Artifacts should have:
  • kind;
  • label;
  • storage path/URL;
  • content hash;
  • redaction level;
  • metadata;
  • run id;
  • operation id.

Approval integration

Some operations should require approval before execution. Examples:

  • browser submit/click on irreversible action;
  • external integration write;
  • data export;
  • command with network access;
  • command writing large output;
  • action touching production infrastructure;
  • file operation involving sensitive artifacts. Flow:
operation requested
→ policy evaluates require_approval
→ approval request created
→ run pauses
→ Expert/Ops resolves
→ operation continues or fails closed

The capability shell should not bypass approval. It should require a valid policy decision or capability grant.

Threat model

Threat: model tries to read secrets

Controls:

  • no secrets in env;
  • no secrets in binary;
  • no secrets mounted into workspace;
  • path allowlists;
  • OS sandbox;
  • control-plane credential proxy.

Threat: child process exfiltrates data over network

Controls:

  • network off by default;
  • proxy-only mode;
  • egress allowlist;
  • OS network namespace;
  • audit of network operations.

Threat: prompt injection asks model to run dangerous commands

Controls:

  • no raw shell;
  • structured op schema;
  • policy evaluation;
  • approval gates;
  • executable allowlists;
  • sandbox denial events.

Threat: runner binary is stale or tampered

Controls:

  • runner hash allowlist;
  • signed manifest;
  • policy hash check;
  • startup registration;
  • reject stale manifests;
  • provenance events.

Threat: process writes outside workspace

Controls:

  • path normalization;
  • symlink resolution;
  • write allowlists;
  • read-only rootfs;
  • bind mounts;
  • OS sandbox.

Threat: huge output or fork bomb

Controls:

  • output caps;
  • timeout;
  • PID limits;
  • cgroups;
  • process tree cleanup.

Testing requirements

Sandbox/capability runtime must have tests for:

  • path traversal denial;
  • symlink escape denial;
  • absolute path denial;
  • write outside workspace denial;
  • env contains no secrets;
  • raw shell string unsupported;
  • unknown executable denied;
  • timeout kills process tree;
  • output cap enforced;
  • network denied in network-off;
  • proxy-only network cannot reach arbitrary host;
  • stale runner hash rejected;
  • stale policy hash rejected;
  • denied operations emit events;
  • successful operations create artifacts/events.

Deployment considerations

Railway limitations

If Railway cannot provide the necessary sandbox primitives for agent execution, sandbox workers should run somewhere that can. Possibilities:

  • dedicated worker service with restricted container runtime;
  • Kubernetes with hardened pod security;
  • Firecracker/microVM provider;
  • isolated job runner;
  • remote execution host with seccomp/cgroups/network policy. Do not assume a platform sandbox exists just because code runs in a container.

Control-plane communication

The runner should communicate with the control plane through a narrow authenticated protocol. Recommended:

  • mTLS or signed requests;
  • short-lived bootstrap credential;
  • runner registration;
  • operation-level capabilities;
  • replay protection;
  • correlation ids.

Logging

Runtime logs must be treated as potentially sensitive. Rules:

  • redact secrets;
  • cap log size;
  • store full logs as artifacts when needed;
  • do not stream raw logs into model context;
  • include operation ids and event ids.

Minimal viable implementation

The smallest useful version is:

  1. compiled/bundled runner manifest, even if not fully per-build yet;
  2. no secrets in runner env;
  3. control-plane credential proxy for vendor tools;
  4. Zig capability shell with:
    • artifact_read,
    • artifact_write,
    • artifact_list,
    • exec_argv;
  5. path allowlists;
  6. env scrubbing;
  7. timeout/output caps;
  8. structured events;
  9. basic container restrictions. That MVP already materially improves safety over a generic shell.

End-state summary

The end state is not "agents get a safer bash." The end state is:

Agents request structured capabilities.
The control plane authorizes them.
A compiled runner proves its manifest.
A Zig capability shell performs narrow operations.
An OS/container sandbox enforces hard limits.
Secrets stay outside the runtime.
Every action is visible in events, artifacts, and audit.

This architecture makes higher-autonomy workflows possible without giving the model ambient authority over the system.