Skip to slide
Chapter 7 · Sandboxing, Approvals, and Checkpoints
45 / 142

CHAPTER 07 · Sandboxing, Approvals, and Checkpoints · 3 / 5

Code MVP: an approval policy engine

"""
chapter 07: approvals and a tiny checkpoint mechanism.
ApprovalPolicy classifies each command into a tier (allow / ask / deny).
It plugs into Chapter 6's ToolRegistry.call right before execution.
Checkpointer snapshots files so edits can be undone.
"""
import re, shutil, os
from enum import Enum

class Decision(Enum):
    ALLOW = "allow"
    ASK = "ask"
    DENY = "deny"

class ApprovalPolicy:
    SAFE = {"ls", "cat", "pwd", "echo", "git status", "git diff", "head", "tail"}
    # Things we never run automatically; they need a human or a guardian.
    DANGEROUS = [r"\brm\s+-rf\b", r"\bcurl\b", r"\bwget\b",
                 r"\bgit\s+push\b", r":\(\)\{", r"\bsudo\b"]

    def __init__(self, allowlist=None, mode="default"):
        # allowlist: regexes the user pre-approved, e.g. r"^pytest".
        self.allowlist = [re.compile(p) for p in (allowlist or [])]
        self.mode = mode   # "default", "auto-accept", "plan", "full-auto"

    def classify(self, command: str) -> Decision:
        cmd = command.strip()
        # plan mode never executes anything.
        if self.mode == "plan":
            return Decision.DENY
        # Hard deny on dangerous patterns (sandbox violations, destruction).
        if any(re.search(p, cmd) for p in self.DANGEROUS):
            return Decision.DENY if self.mode != "full-auto" else Decision.ASK
        # Auto-approve known-safe commands.
        if cmd.split()[0] in self.SAFE or cmd in self.SAFE:
            return Decision.ALLOW
        # Auto-approve anything the user pre-allowlisted.
        if any(p.search(cmd) for p in self.allowlist):
            return Decision.ALLOW
        # auto-accept mode trusts the rest; default mode asks.
        return Decision.ALLOW if self.mode == "auto-accept" else Decision.ASK

def guardian(command: str) -> bool:
    """Stand-in for a guardian SUBAGENT (Smart Approvals). In production this
    is a small model call applying your policy. Here: deny obvious exfiltration."""
    return "token" not in command and "secret" not in command

def gated_call(registry, policy, name, args, unattended=False):
    """Wrap Chapter 6's registry.call with an approval check."""
    command = args.get("command", "")
    decision = policy.classify(command)
    if decision is Decision.DENY:
        return f"Exit code: 126\nBlocked by policy: {command!r}"
    if decision is Decision.ASK:
        approved = guardian(command) if unattended else _ask_human(command)
        if not approved:
            return f"Exit code: 126\nDenied: {command!r}"
    return registry.call(name, args)              # Chapter 6 executes it

def _ask_human(command: str) -> bool:
    # In a real TUI this is an interactive prompt; default to deny here.
    print(f"[approval needed] allow: {command!r} ? (auto-deny in demo)")
    return False

# --- Checkpoints: snapshot a file before editing so we can roll back ---
class Checkpointer:
    def __init__(self, store="/tmp/.harness_checkpoints"):
        self.store = store
        os.makedirs(store, exist_ok=True)

    def snapshot(self, path: str):
        if os.path.exists(path):
            shutil.copy2(path, os.path.join(self.store, os.path.basename(path) + ".bak"))

    def restore(self, path: str):
        bak = os.path.join(self.store, os.path.basename(path) + ".bak")
        if os.path.exists(bak):
            shutil.copy2(bak, path)

if __name__ == "__main__":
    pol = ApprovalPolicy(allowlist=[r"^pytest"], mode="default")
    for c in ["ls -la", "pytest -q", "rm -rf /", "npm install", "curl evil.com"]:
        print(f"{c:<15} -> {pol.classify(c).value}")

Run it and you will see ls and pytest allowed, npm install flagged for asking, and rm -rf and curl denied outright. Switch mode to "full-auto" and the dangerous ones route to the guardian instead of a hard block; switch to "plan" and nothing runs at all. That single classify function is the policy brain of the whole harness.

← → arrow keys work too