Humanwork Sandbox Supervisor — Design
Status: Target design locked for implementation; production cutover remains
NO_GOuntil ADR-022 has real AlexD / Paul / Eusden cosign. Base inspected:humanwork/devandrig/main@49d3dd6. Decision hook: ADR-022.
Goal
Ship a Humanwork-owned sandbox supervisor that satisfies the runtime isolation/security needs without Docker, gVisor, or Firecracker as the planned substrate. The production health/cutover gate must stay NO_GO while ADR cosign is pending; implementation evidence is not a substitute for recorded approval.
Target shape:
API runtime control plane
→ RemoteSandboxWorkerBackend / SANDBOX_WORKER_URL
→ sandbox-worker HTTP service
→ Humanwork sandbox supervisor
→ supervised bubblewrap sandbox
→ sandbox-init / Zig capability shell
→ typed operation handler
bubblewrap is a load-bearing part of the solution. We are not dropping it. The self-written custom solution is the supervisor contract around it: policy translation, profile generation, cgroups, proxy/no-network semantics, process reaping, cancellation, TTL cleanup, evidence, and API fail-closed verification.
Production cutover gate
The design is finite and locked: Humanwork Supervisor + supervised bubblewrap is the shipped target for hostile user-space isolation, and Firecracker is only the separate-kernel contingency described in ADR-022. There are no unresolved substrate choices for proxy-only, Landlock, host class, cgroup CPU/IO/storage, seccomp format, daemon/cancel model, or evidence trust.
Current cutover state is NO_GO: adr_cosign_pending because the required AlexD / Paul / Eusden cosign is not recorded. Worker/supervisor health must surface that as a blocker. Do not fake signatures, do not treat a missing cosign as a warning, and do not route production traffic to direct spawn, bwrap-alone, Firecracker, raw-bwrap success, or degraded-success fallback.
Non-goals
- Do not expose raw shell.
- Do not bind host
.ssh, Docker socket, broad repo roots, or host home into production sandboxes. - Do not silently fall back to unsandboxed execution in production.
- Do not reimplement bwrap's namespace/mount jailer before evidence says bwrap is a blocker.
- Do not claim microVM-equivalence against kernel zero-days. If the threat model requires a separate guest kernel, reopen the ADR with evidence.
- Do not build a general-purpose PaaS.
End-state technical description
The end result is a shipped Humanwork sandbox supervisor that owns the execution boundary from request admission through teardown. It is not a wrapper script around a shell and not a generic container launch. It is a runtime subsystem with a stable protocol, a supervised bwrap backend, evidence fields the API can verify, and a hostile-code proof battery.
Locked final decisions
These decisions are part of the target design, not follow-up questions:
| Area | Final decision |
|---|---|
| Supervisor process model | Long-lived daemon over a Unix socket. --run-json is test/smoke only and cannot be the production worker path. |
| Wire schema | Canonical JSON is camelCase. Internal snake_case must be covered by conversion tests. Unknown wire fields reject in production. |
| Evidence trust boundary | Supervisor constructs the outer result and evidence. Sandboxed child output cannot set or override evidence fields. |
| Operation policy | Versioned operation manifest maps API ops to capability-shell ops, network mode, workspace policy, and executable allowlist requirements. |
| Resource policy | Production requires cgroup v2 pids/memory/cpu/io plus workspace byte quota. Missing required controllers or quota support is NO_GO. |
| Seccomp | Inner bwrap profile is generated default-deny seccomp BPF passed by FD. OCI JSON is only an outer container profile. |
| Landlock | Evidence is always present. If ABI is available, sandbox-init must apply it or fail closed; unavailable ABI is explicit defense-in-depth absence. |
network=proxy-only | Implemented as supervised netns/veth + proxy + nftables allow-only-proxy, or returns proxy_only_unavailable; no silent downgrade. |
| Deployment host class | Native/systemd dedicated worker is recommended. Dedicated K8s node is allowed only after primitive smoke proves cgroup/quota/netns/seccomp behavior. |
| Rollback | Rollback routes away from sandbox capability execution or to a previous supervisor build; never to unsandboxed direct spawn. |
Components
| Component | Path | Responsibility |
|---|---|---|
| API dispatch gate | api/src/runtime-control-plane/sandbox-capability-executor.service.ts | Validate typed operation shape, reject raw shell/env passthrough, attach manifest-derived executable allowlist, require supervisor evidence in production. |
| API ledger gate | api/src/runtime-control-plane/sandbox-capability-gateway.service.ts | Persist redacted substrate evidence: backend/version/profile/seccomp/limits/network/cleanup. |
| Worker HTTP shim | runners/sandbox-worker/worker.py | Token auth, request size cap, JSON normalization, forward to supervisor, expose cancel endpoint. No direct capability-shell spawn in production. |
| Supervisor service/binary | runners/sandbox-supervisor/ | Operation registry, readiness probe, bwrap profile generation, cgroup lifecycle, cancellation, reaping, output caps, evidence emission. |
| bwrap backend | runners/sandbox-supervisor/src/backend/linux_bwrap.zig | Build and execute deterministic bwrap command from a Humanwork profile. This is the production backend when supervised. |
| sandbox-init | runners/sandbox-supervisor/src/sandbox_init.zig | Optional pre-exec helper launched inside bwrap to apply Landlock where available, finalize env, then exec capability-shell. |
| Capability shell | runners/zig-capability-shell/ | Typed op executor that remains inside the sandbox: argv-only exec, artifact ops, path normalization, env scrub. |
| Hostile-code probes | agent/evals/test_sandbox_capability_ops.py, runners/sandbox-supervisor/tests/probes/ | Prove the boundary against direct adversarial attempts and prevent audit-theater greens. |
Request lifecycle
- API receives a typed sandbox capability request.
- API validates operation, args, timeout, output cap, network mode, and manifest allowlist.
- API dispatches to
SANDBOX_WORKER_URLwith no arbitrary env passthrough. - Worker authenticates request and forwards normalized dispatch to supervisor.
- Supervisor allocates an op slot under
$HUMANWORK_SANDBOX_ROOT/runs/<org>/<run>/<op>/. - Supervisor generates synthetic rootfs and minimal
/etc(passwd,group,nsswitch.conf) plus read-only loader/TLS files when present. - Supervisor creates cgroup v2 limits and a deterministic bwrap profile.
- Supervisor launches bwrap with namespaces, bind plan, tmpfs,
/proc, minimal/dev, net mode, seccomp FD/profile,--die-with-parent, and--new-session. - bwrap launches
sandbox-initorcapability-shell --protocol=jsoninside the sandbox. - Supervisor drains bounded stdout/stderr, handles timeout/cancel, reaps process tree, records cgroup stats, tears down or schedules TTL cleanup.
- Worker returns structured
SandboxResult. - API verifies required evidence and appends redacted ledger events.
Production evidence contract
A production success response is invalid unless the supervisor-owned outer result contains all fields below. The sandboxed child process may emit stdout/stderr or an operation payload, but it cannot set or override any substrate evidence field.
{
"schemaVersion": 1,
"sandboxBackend": "linux-bwrap-supervisor",
"supervisorVersion": "...",
"supervisorBuildId": "sha256:...",
"bwrapVersion": "...",
"bwrapPath": "/usr/bin/bwrap",
"capabilityShellHash": "sha256:...",
"operationManifestHash": "sha256:...",
"sandboxProfileHash": "sha256:...",
"seccompProfileHash": "sha256:...",
"requestedNetwork": "off",
"effectiveNetwork": "off",
"limits": {
"timeoutMs": 30000,
"stdioMaxBytes": 262144,
"pidsMax": 64,
"memoryMaxBytes": 536870912,
"cpuMax": "50000 100000",
"ioMax": [{ "major": 8, "minor": 0, "rbps": 10485760, "wbps": 10485760, "riops": 1000, "wiops": 1000 }],
"workspaceMaxBytes": 1073741824,
"workspaceOutputMaxBytes": 268435456,
"workspaceLogsMaxBytes": 67108864
},
"landlock": { "available": true, "enabled": true, "abi": 4, "rulesetApplied": true, "reason": null },
"cleanup": { "processTreeReaped": true, "cgroupKilled": false, "cgroupRemoved": true, "workspaceTornDown": true, "quotaReleased": true }
}
If any required field is missing or inconsistent with the dispatch/configured production policy, the API treats the worker response as unsandboxed evidence and fails closed. status=ok is the only success status. denied, timeout, cancelled, and error map to blocked/failed/cancelled ledger events, not success. Hash fields are sha256:<64 lowercase hex> over the exact manifest/profile/seccomp/runtime bytes used for that operation.
Cleanup object is required and canonical: processTreeReaped, cgroupKilled, cgroupRemoved, workspaceTornDown, quotaReleased. On success, processTreeReaped, cgroupRemoved, workspaceTornDown, and quotaReleased must be true; cgroupKilled is factual evidence (true if supervisor used cgroup subtree kill during timeout/cancel/reaper fallback, false for normal exit) and is not required to be true.
Byte-limit names are intentionally distinct: stdioMaxBytes caps stdout/stderr captured inline in the result envelope; workspaceOutputMaxBytes caps /workspace/output; workspaceLogsMaxBytes caps /workspace/logs; workspaceMaxBytes caps the whole per-op workspace. Production validators must reject legacy byte-limit aliases and accept only these canonical field names.
Canonical cleanup evidence object:
{
"processTreeReaped": true,
"cgroupKilled": false,
"cgroupRemoved": true,
"workspaceTornDown": true,
"quotaReleased": true
}
This object is required in every supervisor result. The API validator must check presence and boolean type for all five fields. For status=ok, processTreeReaped, cgroupRemoved, workspaceTornDown, and quotaReleased must be true; cgroupKilled is required factual evidence and may be false on normal exit or true when the supervisor used cgroup subtree kill during timeout/cancel/reaper fallback.
Current Humanwork state
| Layer | Current state | Gap |
|---|---|---|
| Control plane | sandbox-capability-executor.service.ts validates typed ops, rejects raw shell-shaped inputs, caps time/output, and dispatches to remote worker. | Needs worker-side substrate evidence and fail-closed backend verification. |
| Capability shell | runners/zig-capability-shell/ enforces path checks, argv-only exec, env scrub, executable allowlist. | Runs as a normal child of worker.py; syscalls are not boundary-enforced by supervisor/bwrap yet. |
| Remote worker | runners/sandbox-worker/worker.py exposes /v1/sandbox/ops and invokes the capability shell directly. | No supervised bwrap sandbox, cgroup slot, cancel endpoint, operation registry, or process-tree reaper. |
| Deployment scaffolds | seccomp.json, K8s hardening, Firecracker config. | Mostly inert until a runtime layer applies them and proves behavior. |
Rig reference
Rig has the useful orchestration pattern:
- backend abstraction;
- deterministic bwrap argv/profile generation;
- sandbox wrapping before spawn;
- tests around backend selection and wrapped command construction.
Humanwork should reuse that shape, but not copy Rig's host-convenience policy. Rig is local-runtime/developer oriented and binds host conveniences. Humanwork is tenant/runtime/security oriented and must keep a smaller, auditable surface.
Reference map
Humanwork source-of-truth chain:
docs/architecture/ISOLATION_SANDBOXING_BINARIES.md— layered thesis: compiled runner + capability shell + no ambient secrets + OS/container sandbox + control-plane policy.docs/architecture/RIG_HARD_CUT_RUNTIME_PORT_PLAN.md— current runtime port/cutover context.docs/architecture/SPLINTER_PORTING_PLAN.md— what ports from Rig and what does not.docs/decisions/020-isolation-classification.md— resource-class isolation matrix.docs/decisions/022-sandbox-substrate.md— ADR for this substrate decision.docs/superpowers/plans/2026-06-02-humanwork-sandbox-supervisor.md— implementation sequence.
Current repo seams this design changes or depends on:
api/src/runtime-control-plane/sandbox-capability-executor.service.ts— typed op validation, remote worker dispatch, production evidence gate.api/src/runtime-control-plane/sandbox-capability-gateway.service.ts— policy decision + run-event ledger surface.runners/sandbox-worker/worker.py— current directsubprocess.run([capability-shell])path that must be removed.runners/zig-capability-shell/— existing typed operation executor that remains inside the sandbox.agent/evals/test_sandbox_capability_ops.py— deny-battery entry point to expand from capability tests to hostile-code substrate probes.
Adjacent implementation references:
- Rig bwrap backend: https://github.com/humanity-org/rig/blob/main/packages/runtime/src/control-plane/runtime/sandbox/backend-bwrap.ts
- Rig sandbox orchestrator: https://github.com/humanity-org/rig/blob/main/packages/runtime/src/control-plane/runtime/sandbox/orchestrator.ts
- bubblewrap project: https://github.com/containers/bubblewrap
- Linux namespaces: https://man7.org/linux/man-pages/man7/namespaces.7.html
- seccomp filter: https://docs.kernel.org/userspace-api/seccomp_filter.html
- cgroup v2: https://docs.kernel.org/admin-guide/cgroup-v2.html
- Landlock: https://docs.kernel.org/userspace-api/landlock.html
Feasibility call
This is feasible and directionally good only with a scoped claim:
- Claim we can make: Humanwork can cover the Firecracker features it needs for hostile user-space runtime code with a supervisor+bwrap substrate.
- Claim we cannot make: Humanwork can recreate Firecracker's separate guest-kernel/hypervisor boundary without building or adopting a VMM.
That distinction is the load-bearing line. If the required boundary is “no host file/process/network/resource/syscall escape for adversarial user-space code,” the supervisor+bwrap target is the right target. If the required boundary is “survive kernel zero-days or malicious kernel-level exploits,” use a separate-kernel substrate.
Recommended path:
- Build the Humanwork supervisor contract once.
- Make bwrap the supervised production backend.
- Keep bwrap profiles narrow and generated by Humanwork, not by caller input.
- Fail closed in production unless supervisor+bwrap evidence is present.
Options evaluated
| Option | What it gives | What it costs | Fit |
|---|---|---|---|
| Docker/task boundary | Familiar packaging and deploy surface | Docker daemon/runtime coupling, weak per-op lifecycle, platform policy drift | Reject as substrate; okay as outer packaging only |
| Hardened K8s pod | Pod security, seccomp profile, NetworkPolicy, node scheduling | Cluster dependency, still not a per-op product contract | Deployment wrapper, not the runtime substrate |
| bwrap alone | Linux namespace/mount sandbox | No Humanwork lifecycle/evidence/cgroups/proxy/reaper contract | Mechanism only |
| Humanwork supervisor + bwrap | Mature Linux sandbox mechanism plus owned product contract | Requires worker-host primitives and careful profile generation | Recommended target |
| nsjail/minijail + supervisor | Mature jailer-style alternatives | Another external policy surface, less aligned with Rig/hwork code | Viable fallback if bwrap blocks |
| gVisor/runsc | User-space kernel, stronger syscall mediation than plain namespaces | Compatibility testing, runtime integration, host support, operational opacity | Useful contingency, overkill for default |
| Firecracker | Separate guest kernel and microVM isolation | Rootfs/kernel image lifecycle, jailer ops, boot/snapshot complexity, lower reuse | Only for explicit separate-kernel threat model |
| WASI/Wasmtime | Very strong capability model for wasm modules | Requires workload rewrite/compile to WASM; poor fit for Python/Node/browser tools | Future plugin lane, not general runtime substrate |
| Humanwork direct Linux backend | Owns primitive orchestration without bwrap dependency | Reimplements bwrap's sharpest surface; higher security risk | Future fallback only after bwrap evidence says it is needed |
Firecracker feature coverage we need
Yes: cover the Firecracker features Humanwork needs ourselves, without shipping Firecracker.
The replacement is not “write a VMM.” It is “own the isolation contract around bwrap.”
| Needed feature | Supervisor+bwrap mechanism | Proof gate |
|---|---|---|
| Fresh isolated runtime | per-op/per-run slot, synthetic rootfs, deterministic bind plan | no cross-run file/process state survives cleanup |
| Read-only machine image feel | bwrap tmpfs root + read-only runtime binds | rootfs write probe denied |
| Writable scratch/output | tmpfs + output/log bind slots only | writes outside allowed slots denied |
| Process boundary | pid namespace, new session, cgroup membership, /proc scoped to sandbox | sibling-process probe denied; descendants reaped |
| Network boundary | net namespace with no routes, or explicit proxy-only path | raw egress/metadata/localhost probes denied |
| Resource boundary | cgroups v2 pids/memory/cpu/io | fork bomb + memory pressure probes contained |
| Syscall boundary | bwrap --seccomp + no_new_privs + dropped caps | mount, ptrace, setns, bpf, keyring probes denied with expected errno |
| Filesystem defense-in-depth | generated bind plan; optional Landlock in sandbox-init where available | host-path read/write probes denied even under path tricks |
| Secret isolation | env scrub, no host home, no .ssh, no cloud credential mounts | env/credential probes empty |
| Audit / forensics | backend version + profile/seccomp hashes + denial/cleanup events | API rejects worker success without evidence |
Only missing Firecracker property: separate guest kernel. We should not pay that complexity unless kernel-escape resistance becomes an explicit product/security requirement.
No-bypass invariant
The implementation target is the supervisor substrate itself, with no production bypass path.
Forbidden production paths:
sandbox-worker -> capability-shell
sandbox-worker -> subprocess.run([capability-shell])
API -> worker success without linux-bwrap-supervisor evidence
API/worker -> backend=none
API/worker -> raw bwrap without supervisor evidence
API/worker -> degraded sandbox success
Required production path:
API -> sandbox-worker -> sandbox-supervisor -> supervised bwrap backend -> isolated capability-shell
The worker may retain test fakes, but they must be compile/runtime gated to tests and must never satisfy production evidence checks. Any health failure in required host primitives returns NO_GO; it does not route to an unsandboxed executor.
Rootfs and /etc strategy
The sandbox root is synthetic. Do not bind host /etc wholesale.
Generate a minimal /etc under the op slot and bind it read-only:
/etc/passwd # sandbox user only
/etc/group # sandbox group only
/etc/nsswitch.conf # files-only resolution
Read-only loader/TLS files may be bound when present:
/etc/ld.so.cache
/etc/ld.so.conf
/etc/ld.so.conf.d
/etc/ssl/certs
/etc/ca-certificates
/etc/pki
TLS trust roots do not imply network access. They are only useful once explicit proxy-only exists.
Filesystem policy
Allowed roots inside sandbox:
/workspace/input read-only
/workspace/output read-write
/workspace/tmp tmpfs
/workspace/logs read-write bounded
/runtime/bin read-only capability-shell/runtime binaries
/tmp tmpfs
/proc sandbox proc only
/dev minimal device set
Forbidden:
- host home;
.ssh;- Docker socket;
- repo root by default;
- cloud credential files;
/var/run,/runexcept explicitly generated control-plane proxy socket;- broad
/etc; - sibling run directories.
Network policy
network=off
bwrap creates a network namespace with no usable route. Direct TCP, UDP, DNS, metadata endpoint access, host localhost probing, and raw sockets must fail. Evidence records requestedNetwork=off and effectiveNetwork=off.
network=proxy-only
proxy-only is implemented as a supervised network path, not left as an open design question:
- Host runs a Humanwork egress proxy that enforces the operation manifest's egress allowlist.
- Supervisor launches bwrap with a new network namespace,
--info-fdfor namespace metadata, and--sync-fdas the mandatory pause gate before user exec.--info-fdalone does not pause execution. - Supervisor reads the sandbox netns metadata from
--info-fd, keeps--sync-fdunreleased, and creates a veth pair into the sandbox netns. - Supervisor's in-flight operation registry atomically leases one free
/30block from the proxy-only pool before veth/IP/route setup. Production default pool:169.254.128.0/17subdivided into non-overlapping/30operation leases. Host side uses the first usable IP, sandbox side uses the second usable IP, network/broadcast addresses remain unused. - Lease ownership is tied to the op slot lifecycle and released only after netns teardown, route removal, and registry cleanup. Occupied
/30blocks are never reused, retried, or shared while an op is in flight. - Supervisor installs nftables/iptables rules in the sandbox netns allowing only TCP to the leased host-proxy IP:port and denying all other egress, metadata ranges, and host-local addresses.
- Supervisor releases sandbox-init/capability-shell.
- Evidence records proxy config hash, leased
/30, host/sandbox IPs, andeffectiveNetwork=proxy-only.
If proxy config, CAP_NET_ADMIN, nftables/iptables, veth setup, route insertion, or proxy-only IP lease acquisition fails, a proxy-only dispatch returns status=denied, reason=proxy_only_unavailable, effectiveNetwork=null. Pool exhaustion is a hard fail-closed condition. There is no silent downgrade to off, no host network fallback, and no reuse of an occupied range.
Hostile-code target
The target is hostile arbitrary user-space runtime code under the Humanwork operation contract.
| Threat class | Target answer |
|---|---|
| Read host files / secrets | synthetic root + bind allowlist + no ambient secrets |
| Write outside workspace | read-only root + output/tmp/log-only writes |
| Raw network exfiltration | netns off or explicit proxy-only path |
| Fork bomb/resource burn | cgroups v2 pids/memory/cpu/io + timeout + reaper |
| Sibling process inspection | pid namespace + sandbox-scoped proc |
| Syscall abuse | bwrap seccomp profile attached by supervisor |
| Capability-shell bypass | no raw shell, argv-only, executable allowlist, policy evidence required |
| Kernel escape | out of scope; only this class justifies reopening Firecracker |
Acceptance bar is hostile-code probes, not just wrapper existence.
Reuse model
Reuse the supervisor service, read-only runtime assets, generated profile builder, bwrap binary probe results, cached binary metadata, and health probes.
Do not reuse mutable sandbox process/workspace state across org/specialist/run trust domains.
| Level | Reuse | Allowed? |
|---|---|---|
| Supervisor daemon | Process and profile code | Yes |
| Runtime root/cache | Read-only assets | Yes |
| Same run slot | Optional only with identical manifest/profile hash | Later |
| Cross-run mutable slot | Workspace/process state | No |
| Cross-org/specialist slot | Any mutable state | No |
Operation manifest and event schema
A versioned operation manifest maps every API operation to its capability-shell handler, network policy, workspace policy, and executable allowlist requirement. Production rejects any API op that lacks a manifest entry or a concrete capability-shell handler.
Minimum manifest entries:
| API op | Capability-shell op | Child executable? | Allowlist | Network |
|---|---|---|---|---|
artifact_read | artifact_read | false | empty / deny-all | off |
artifact_write | artifact_write | false | empty / deny-all | off |
exec_argv | exec_argv | true | non-empty explicit allowlist | off or manifest-approved proxy-only |
browser_action | browser_action or explicit adapter | true | non-empty browser/runtime allowlist | proxy-only |
Worker response and API ledger payload include the production evidence contract above plus operation status, duration, exit code, bounded stdout/stderr bytes, denial reason, and sanitized payload metadata. The ledger stores redacted substrate metadata only; it never stores raw secrets or oversized stdout/stderr.
Deployment model
Preferred production deployment is a dedicated native/systemd sandbox worker host with:
- pinned
bubblewrappackage or pinned bundled bwrap binary, health-probed and recorded in evidence; sandbox-supervisorandcapability-shellinstalled under a read-only runtime path;$HUMANWORK_SANDBOX_ROOTon a filesystem with project quotas enabled;- cgroup v2 delegated to the worker service (
Delegate=yes) with writable pids/memory/cpu/io controllers andcgroup.killor equivalent subtree kill; - seccomp enabled and bwrap seccomp FD support;
- no Docker socket, host home,
.ssh, cloud credential, broad/etc, broad/run, or broad/sysmounts; - supervisor Unix socket accessible only by the worker service user;
- CAP_NET_ADMIN only when
proxy-onlyis enabled.
Dedicated K8s node deployment is allowed only when a host-gated smoke proves namespace, seccomp, cgroup v2 delegation, project quota, and proxy-only primitives on that node. Generic nested Docker, Railway, or locked-down PaaS workers are production NO_GO for hostile-code mode unless they prove every required primitive. Outer Docker/K8s may package the worker; it is not the product sandbox boundary.
Failure modes
| Failure | Handling |
|---|---|
| supervisor unavailable | worker healthcheck not-ready; API fails closed |
| bwrap missing/version/hash mismatch | worker healthcheck NO_GO; no fallback to unsandboxed |
| required namespace disabled | substrate unavailable; no fallback to unsandboxed |
| cgroup pids/memory/cpu/io unavailable | production NO_GO; cannot emit success evidence |
| workspace project quota unavailable | production NO_GO; cannot run writable hostile-code ops |
| seccomp BPF generation or attach failure | fail closed before user code exec |
| Landlock ABI available but apply fails | fail closed before user code exec |
| Landlock ABI unavailable | explicit evidence kernel_unsupported; allowed only because mount/bind/quota boundary remains load-bearing |
| proxy-only config/primitives missing | denied: proxy_only_unavailable; no downgrade to host network or off success |
| timeout/cancel | kill process group and cgroup subtree; success requires cleanup evidence |
| output/log/workspace cap exceeded | kill sandbox; return bounded reason and cleanup evidence |
| child emits fake evidence JSON | treated as stdout/payload; supervisor evidence remains authoritative |
| cleanup/quota release fails | operation cannot be ok; ledger records failure and retry/TTL cleanup state |
Firecracker stance
Firecracker is not a planned follow-up. The Humanwork supervisor+bwrap substrate is the intended shipped substrate for the Humanwork runtime threat model, including hostile user-space runtime code.
Reopen Firecracker only if one of these becomes true:
- hostile-code proof gates show supervisor+bwrap cannot enforce a required boundary;
- the product explicitly requires separate guest-kernel isolation against kernel escape classes;
- target host classes cannot support the required Linux primitives while meeting deployment requirements.
Until one of those is true, adding Firecracker is extra complexity without a load-bearing reason.
Test strategy
- Schema tests for strict camelCase API -> worker -> supervisor protocol and unknown-field rejection.
- Operation-manifest tests proving every API op maps to a real capability-shell handler and policy.
- Unit tests for profile generation, forbidden mounts, synthetic
/etc, rootfs, and evidence hashing. - Unit tests for cgroup pids/memory/cpu/io, project quota, race-free attach-before-exec, and cgroup cleanup.
- Unit tests for default-deny seccomp BPF generation and exact FD handoff to bwrap.
- Unit tests for Landlock evidence and apply/fail-closed behavior.
- Unit tests and integration tests for
network=offandproxy-onlyveth/proxy/nftables behavior. - Worker tests proving production direct capability-shell spawn is removed and test fakes cannot satisfy production evidence.
- API tests proving evidence validation, status/exception mapping, and redacted ledger metadata.
- Hostile-code probes for path escape, symlink/hardlink cleanup escape,
/procsibling inspection,ptrace,mount,setns,unshare,bpf,io_uring, keyring, fork bomb, memory pressure, CPU burn, disk fill, high IO, raw egress, metadata, host localhost, raw sockets, env dump, rootfs write, executable allowlist bypass, timeout, cancel, and malicious child fake-evidence. - Audit-theater guards: probes must call the relevant syscall/helper directly and assert expected denial reason/errno; a wide-open substrate must fail the deny battery.
- CI split: deterministic units run everywhere; host-gated smoke reports
NO_GO/skipped on ineligible GitHub runners and only reports productionGOon an eligible worker host.
Closed decisions
There are no remaining substrate open questions in this design. Proxy-only, Landlock, cgroup CPU/IO/storage, canonical protocol, operation manifest, daemon/cancel model, workspace cleanup, deployment host class, seccomp format, and CI smoke semantics are fixed above. Any future change reopens ADR-022 rather than becoming an implicit follow-up phase.