Lab 01 — Sandbox Policy Compiler

Difficulty: 5/5 | Time: 8–12 hours

Pairs with the Phase 06 WARMUP (the whole guide — namespaces, capabilities, seccomp, cgroups, OCI/runc, gVisor, microVMs, control plane) and the HITCHHIKERS-GUIDE. This is the flagship lab of the phase.

Why This Lab Exists (Purpose & Goal)

Running untrusted code is the defining hard problem of modern infrastructure — it is what CI runners, serverless platforms, online code sandboxes, and multi-tenant clouds all do. A sandbox is a controlled reduction of authority, and it is only as strong as its weakest dimension: one missing control (privileged mode, a mounted runtime socket, host networking) defeats all the others. The goal of this lab is to build the compiler that takes a declarative job policy and either produces a hardened execution spec or rejects the configuration with explicit violations — the enforcement point that makes "run this untrusted workload" safe.

The Concept, In the Weeds

Isolation is a composed proof: a conjunction of independent, minimal grants, each enforced outside the attacker's code and verified after a failed attempt (identity, filesystem, kernel surface, network, resources, lifecycle). The compiler's job is to reject any policy where a dimension is left open. The seeded dangerous configurations each defeat isolation in a specific way:

  • privileged/root execution, broad capability (SYS_ADMIN) — re-grants the power the sandbox is supposed to remove;
  • host PID/network namespace, hostPath / runtime-socket mount — the leak principle: hardening one dimension while leaving another open (a mounted Docker socket = host takeover regardless of UID);
  • writable root — persistence and tampering;
  • wildcard egress or a metadata destination — exfiltration and the cloud-credential pivot (the Phase 02 SSRF lesson, at the sandbox boundary);
  • absent memory/PID/CPU/time/output limits — denial of service (availability is security);
  • seccomp disabled/allow-all — the kernel syscall surface left wide;
  • Firecracker mode without jailer, gVisor mode with host networking — using a strong tool while disabling the very thing that makes it strong;
  • a cleanup plan missing a resource — the lifecycle leak (a leftover TAP device is a path back).

The non-negotiable design rule: the compiler never silently "fixes" an unsafe request — it rejects it. Silently rewriting hides the caller's intent and creates policy ambiguity; explicit rejection with a named violation is auditable and teaches the caller.

Why This Matters for Protecting the Company

If the company runs any untrusted or semi-trusted code — customer functions, plugins, build jobs, AI-generated code (Phase 11) — the sandbox is the boundary between "a contained workload" and "cross-tenant compromise or host takeover." A single mis-set flag in a deployment manifest is how real container escapes happen. A policy compiler that gates every workload against the full set of isolation invariants, deny-by-default, is how you make that boundary reliable at scale instead of relying on every engineer to remember every flag. This is the same enforcement-point pattern as the release gate (Phase 03) and admission control (Phase 07), applied to workload isolation.

Run

LAB_MODULE=solution pytest -q
# Then run the hardening experiments in HITCHHIKERS-GUIDE.md on a dedicated Linux VM.

Secure Implementation Patterns

The compiler — reject any policy that defeats one isolation dimension (mirrors solution.py):

FORBIDDEN_CAPS   = {"SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "NET_ADMIN", "DAC_READ_SEARCH"}
FORBIDDEN_MOUNTS = {"/", "/proc", "/sys", "/dev",
                    "/var/run/docker.sock", "/run/containerd/containerd.sock"}  # runtime socket = host takeover
METADATA = {"169.254.169.254", "fd00:ec2::254"}                                 # cloud-credential pivot

def compile_policy(p: SandboxPolicy) -> dict:
    v = []
    if p.run_as_uid <= 0:                       v.append("root-or-invalid-uid")
    if not p.read_only_root:                    v.append("writable-root")
    if p.host_pid:                              v.append("host-pid")
    if p.host_network:                          v.append("host-network")
    if p.capabilities & FORBIDDEN_CAPS:         v.append("broad-capability")
    if any(m in FORBIDDEN_MOUNTS or m.startswith("/home/") for m in p.mounts): v.append("unsafe-host-mount")
    if not p.seccomp_syscalls or "*" in p.seccomp_syscalls: v.append("seccomp-allow-all-or-empty")
    if min(p.memory_mb, p.pids, p.cpu_millis, p.timeout_seconds, p.output_bytes) <= 0:
        v.append("missing-resource-limit")
    if "*" in p.network_destinations:           v.append("wildcard-egress")
    for dest in p.network_destinations:         # block metadata + loopback/link-local control planes
        host = dest.rsplit(":", 1)[0].strip("[]")
        if host in METADATA: v.append("metadata-egress")
        try:
            a = ipaddress.ip_address(host)
            if a.is_loopback or a.is_link_local: v.append("local-control-egress")
        except ValueError: pass
    if p.mode == "firecracker" and not p.jailer: v.append("firecracker-without-jailer")
    if v:
        raise PolicyError(",".join(sorted(set(v))))    # NEVER silently "fix" — reject with named violations
    return { ... "process": {"noNewPrivileges": True, "capabilities": []},
             "root": {"readOnly": True}, "network": {"default": "deny", ...},
             "seccomp": {"default": "deny", "allow": sorted(p.seccomp_syscalls)},
             "cleanup": ["process-tree", "cgroup", "network-namespace", "mounts", "scratch", "api-socket"] }

The compiled spec encodes the secure defaultsnoNewPrivileges: True, empty capabilities, read-only root, network: deny, seccomp: deny — and a complete cleanup plan (verified separately) so the lifecycle leaks nothing.

Production practices to carry forward:

  • Reject, never silently fix — silently rewriting hides caller intent and creates policy ambiguity; a named violation is auditable and teaches the caller.
  • It's a conjunction — one open dimension (a mounted runtime socket, host network, a broad cap) defeats all the others; check every one.
  • Block metadata + RFC1918/link-local egress (the SSRF/credential pivot), default to no network.
  • Match the boundary to the trust — microVM/gVisor for untrusted code; never claim a strength a shared-kernel container doesn't have.

Validation — What You Should Be Able to Do Now

  • State exactly what is shared by a process, a container, a gVisor sandbox, and a microVM — and choose the right isolation level for a workload's trust.
  • Enumerate the dimensions of Linux process authority and give "leak one dimension" failures (mounted socket, inherited descriptor, host namespace).
  • Explain why every unsafe fixture defeats isolation, and why the compiler rejects rather than silently fixes.
  • Reason about gVisor/Firecracker honestly as attack-surface movement, naming what stays trusted.

The Broader Perspective

This lab is the capstone of the deny-by-default thread that runs through the entire curriculum: authority is a conjunction of minimal, independently-enforced, verified grants, and an enforcement point that fails closed on any violation. You met it as the engagement guard (Phase 00), object-level authorization (Phase 02), and the release gate (Phase 03); here it governs the kernel-level isolation of untrusted code, and next it governs cloud admission (Phase 07) and agent tools (Phase 11). The distinguishing senior skill this lab builds is honest threat modeling of isolation — knowing precisely what each technology shares and trusts, so you neither over-claim ("Firecracker prevents all escapes") nor under-protect.

Interview Angle

  • "Design a system to run untrusted customer code multi-tenant." — Deny-by-default at every dimension: a microVM (or at least gVisor) per job; rootless/user-namespaced; all capabilities dropped
    • no_new_privs; tight reviewed seccomp; read-only root + scratch, no host mounts/sockets/devices; no network or an allowlisting egress proxy that blocks metadata; cgroup ceilings; a hardened control plane with per-job short-lived creds; and guaranteed full teardown. State what stays trusted.

Extension (Stretch)

Compile the policy to real rootless-container, gVisor, and Firecracker launch configs, and run the HITCHHIKERS hardening/attack fixtures on a dedicated Linux VM.

References

  • Phase 06 WARMUP and HITCHHIKERS-GUIDE; OCI runtime spec.
  • NIST SP 800-190 (container security); NCC Group "Hardening Linux Containers"; Firecracker NSDI 2020.