# Threat Model

This document defines what Foundry Sandbox protects against, why these protections exist, and how they're implemented.

## Documentation Map

| If you want to... | See... |
|-------------------|--------|
| Understand what threats exist and how they're addressed | This document |
| Learn how each security pillar works | [Security Architecture](security-architecture.md) |
| Understand credential isolation in depth | [Credential Isolation](credential-isolation.md) |
| Configure network modes and domains | [Network Isolation](network-isolation.md) |
| Get a quick security overview | [Security Overview](index.md) |

---

## What We're Protecting

### Assets at Risk

| Asset | Risk Level | Protection |
|-------|------------|------------|
| Host filesystem | Critical | Read-only root, container isolation |
| Git history | High | Read-only filesystem, unified-proxy force-push blocking |
| Production credentials | High | Sandboxes don't have access by default |
| Other projects | Medium | Branch isolation (deny-by-default ref validation, output filtering), separate Docker networks |
| System stability | Medium | Resource limits, no root access |

### Threat Actors

The primary "threat actor" is not malicious - it's **AI coding assistants** that may:

1. **Hallucinate dangerous commands** - An AI might suggest `rm -rf /` or `git reset --hard` without understanding the consequences
2. **Act without full context** - The AI may not know it's in a sandbox vs. production
3. **Make honest mistakes** - Just like humans, but potentially faster and at scale
4. **Follow user instructions too literally** - "Clean up the repo" could be interpreted destructively

This is not about defending against intentional attacks. It's about providing guardrails for well-intentioned but error-prone automation.

---

## Security Pillar Quick Reference

Each pillar blocks specific threat categories. For implementation details, see [Security Architecture](security-architecture.md).

| Pillar | What It Blocks | What It Doesn't Block |
|--------|----------------|----------------------|
| **Read-only Filesystem** | Filesystem writes, system modification, persistent malware | Writes to tmpfs mounts (/tmp, /home/ubuntu) |
| **Network Isolation** | Unauthorized egress, direct external access, DNS exfiltration, IP spoofing/ARP poisoning (CAP_NET_RAW dropped) | Traffic to allowed domains (GitHub, AI APIs); normal TCP/UDP networking |
| **Sudoers Allowlist** | Arbitrary sudo commands, privilege escalation | Allowed commands (apt-get install, service management) |
| **Credential Isolation** | Credential theft, API key exfiltration, env var scraping | Authorized API calls via proxy |
| **Branch Isolation** | Cross-sandbox branch access, unauthorized ref checkout, branch listing leaks | Access to well-known branches (main, master, develop, etc.) and tags |
| **Git Safety** | Force pushes to protected branches, branch/tag deletion, dangerous GitHub API operations | Git operations on sandbox's own branch |

---

## Threat Landscape

### Threat-to-Defense Matrix

| Threat | Primary Defense | Secondary Defense | Details |
|--------|-----------------|-------------------|---------|
| Filesystem destruction | Read-only Filesystem | Sudoers Allowlist | [Filesystem Threats](#1-filesystem-destruction) |
| Local git destruction | Read-only Worktree | — | [Git Threats](#2-git-operations) |
| Remote git destruction | Unified proxy (force-push blocking) | — | [Git Threats](#2-git-operations) |
| Credential theft | Credential Isolation | Network Isolation | [Credential Threats](#3-credential-theft) |
| Supply chain attacks | Credential Isolation | Network + CAP_NET_RAW | [Supply Chain](#4-supply-chain-attacks) |
| Lateral movement | Network (ICC=false) | CAP_NET_RAW dropped | [Lateral Movement](#5-lateral-movement) |
| Cross-sandbox branch access | Branch Isolation | Output filtering | [Branch Isolation](#branch-isolation) |
| Session hijacking | IP binding | CAP_NET_RAW dropped | [Session Attacks](#6-session-token-attacks) |
| DNS exfiltration | Network (DNS filter) | Domain allowlist | [DNS Exfiltration](#7-dns-exfiltration) |
| Sudo escalation | Sudoers Allowlist | Read-only Filesystem | [Sudo Escalation](#8-sudo-escalation) |
| Proxy pack parsing exploit | Pack size limit + timeout | Fail-closed, isolated tempdir | [Proxy-Side Attack Surface](#proxy-side-attack-surface) |

---

### 1. Filesystem Destruction

AI assistants execute bash commands. Any command the AI runs could potentially delete files, overwrite history, or modify system state.

**Attack Vectors:**
- `rm -rf /` or `rm -rf .`
- `git clean -f` - Deletes untracked files
- `sudo rm` - Bypasses user permissions

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Read-only Filesystem | All writes fail with "Read-only file system" error |
| Secondary | Sudoers Allowlist | `sudo rm` is not permitted |

**Why This Works:** The read-only filesystem stops all writes regardless of how the command is invoked. See [Read-only Filesystem](security-architecture.md#read-only-filesystem) for implementation.

---

### 2. Git Operations

Git commands can destroy work that may be unrecoverable. We distinguish between **local** destruction (affects your worktree) and **remote** destruction (affects collaborators).

**Attack Vectors:**

| Attack | Scope | Impact |
|--------|-------|--------|
| `git reset --hard` | Local | Discards uncommitted changes |
| `git clean -f` | Local | Deletes untracked files |
| `git checkout -- <file>` | Local | Discards file changes |
| `git push --force` | Remote | Overwrites remote history (affects collaborators) |
| `git push :refs/heads/main` | Remote | Deletes remote branch |

**Defense Layers:**

*Local git destruction:*

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Read-only Worktree | Worktree files cannot be modified—`git reset --hard` silently fails |

*Remote git destruction:*

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Git proxy (unified-proxy) | Blocks force pushes to protected branches, blocks all branch/tag deletions |
| Secondary | Protected branch enforcement | Blocks ALL direct pushes to main/master/release/production (not just force pushes) |
| Tertiary | Bot mode restrictions | In bot mode, pushes restricted to `sandbox/*` branches only |

**Why This Works:** For local destruction, the read-only filesystem is the actual security control—writes fail regardless of how commands are invoked. For remote destruction, the unified proxy's `git_proxy` addon parses git pkt-line protocol data to detect and block force pushes, branch deletions, and unauthorized repository access. See [Read-only Filesystem](security-architecture.md#read-only-filesystem) for local protection details.

#### Git Shadow Mode

In credential isolation mode, sandboxes cannot directly access the `.git` directory or run git commands against the local repository. Instead, all git operations are proxied through the unified-proxy's git API server.

**How it works:**

1. **`.git` is hidden** — The worktree's `.git` file (a gitdir pointer) is bind-mounted to `/dev/null`, preventing the sandbox from reading repository metadata or objects directly
2. **Git wrapper intercepts commands** — `stubs/git-wrapper.sh` is mounted at `/usr/local/bin/git`, taking precedence over `/usr/bin/git`. For any git command under `/workspace`, the wrapper proxies the request to the git API server
3. **Git API server** — The unified-proxy runs a git API endpoint on port 8083 that receives JSON-encoded git commands, validates them, and executes them against the bare repository
4. **HMAC-SHA256 authentication** — Each request is signed with a per-sandbox HMAC secret. The signature covers the HTTP method, path, request body hash, timestamp, and a random nonce to prevent replay attacks
5. **Policy enforcement** — Before executing any command, the git API applies the same policy checks as the HTTPS proxy: repo authorization, force-push blocking, branch deletion blocking, and bot mode branch restrictions

**What the proxy enforces:**

| Policy | Effect |
|--------|--------|
| Repo authorization | Container can only access repos listed in its registration metadata |
| Branch deletion blocking | `git push` that would delete any branch or tag is rejected |
| Force-push blocking | Non-fast-forward pushes to protected branches are rejected |
| Bot mode | When `auth_mode=bot`, pushes restricted to `sandbox/*` branches |
| Push size limits | Pushes exceeding 100MB are rejected (413 response) |
| Malformed payload rejection | Pushes with unparseable pkt-line headers are rejected (fail closed) |

#### Git HTTPS Credential Injection

For remote git operations (`git push`, `git pull`), the unified-proxy injects `FOUNDRY_PROXY_GIT_TOKEN` into HTTPS requests to GitHub. The sandbox never receives the real GitHub token — it is held exclusively by the proxy container and injected at the network level during credential interception.

#### Branch Isolation

In credential isolation mode, each sandbox is restricted to its own branch via the `branch_isolation.py` module. This prevents one sandbox from accessing, modifying, or even seeing another sandbox's branches.

**How it works:**

1. **Deny-by-default ref validation** — For commands that reference branches (checkout, switch, fetch, pull, merge, rebase, cherry-pick), the validator checks that all ref arguments are allowed: the sandbox's own branch, well-known branches (main, master, develop, production, release/*, hotfix/*), and tags
2. **SHA reachability enforcement** — SHA arguments are validated to ensure they are ancestors of allowed branches, preventing access to commits on other sandboxes' branches
3. **Output filtering** — Commands that list refs (`git branch`, `for-each-ref`, `ls-remote`, `show-ref`, `log --decorate`) have their output filtered to hide disallowed branches
4. **Fail-closed** — Sandboxes without branch identity metadata in their container registration cannot execute git operations

**Defense layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Deny-by-default ref validation | Git commands can only reference allowed branches |
| Secondary | SHA reachability | SHA args verified as ancestors of allowed branches |
| Tertiary | Output filtering | Branch listings hide other sandboxes' branches |
| Fail-safe | Missing metadata check | No branch identity = no git operations |

---

### 3. Credential Theft

A compromised sandbox could attempt to steal credentials and exfiltrate them.

**Attack Vectors:**
- Reading environment variables (`$GITHUB_TOKEN`, `$ANTHROPIC_API_KEY`)
- Searching filesystem for `.env` files or config with secrets
- Memory scraping of processes
- Intercepting network traffic to capture credentials in transit

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Credential Isolation | Real credentials never enter sandbox |
| Secondary | Network Isolation | Cannot exfiltrate to unauthorized destinations |
| UX | Credential Redaction | Masks secrets in command output |

**Why This Works:** Sandboxes receive placeholder values (`CREDENTIAL_PROXY_PLACEHOLDER`) instead of real API keys. The unified-proxy container holds real credentials and injects them into outbound requests. Even if code reads the environment, it gets nothing useful. See [Credential Isolation](credential-isolation.md) for the complete architecture.

---

### 4. Supply Chain Attacks

Malicious npm packages, Python libraries, or other dependencies may contain code that attempts credential theft or data exfiltration.

**Attack Vectors:**
- Package postinstall scripts that read environment variables
- Dependencies that phone home with system information
- Typosquatting packages with malicious payloads
- Compromised maintainer accounts pushing malicious updates

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Credential Isolation | Package reads placeholder values, not real credentials |
| Secondary | Network Isolation | Cannot exfiltrate to unauthorized domains |
| Tertiary | CAP_NET_RAW dropped | Cannot use raw sockets to bypass network controls |

**Why This Works:** When a malicious package runs `process.env.ANTHROPIC_API_KEY`, it gets `CREDENTIAL_PROXY_PLACEHOLDER`. When it tries to send this to `evil.com`, network isolation blocks the request. Even if it attempts raw packet crafting to bypass network rules, CAP_NET_RAW is dropped. See [Supply Chain Attack Scenario](credential-isolation.md#scenario-1-malicious-npm-package-in-sandbox-supply-chain-attack) in the credential isolation threat model for a detailed walkthrough.

---

### 5. Lateral Movement

One compromised sandbox could attempt to attack or access another sandbox running on the same host.

**Attack Vectors:**
- Direct container-to-container network connections
- ARP spoofing to intercept traffic meant for other containers
- IP spoofing to impersonate another container
- DNS rebinding to redirect traffic

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | ICC=false | Docker blocks L3/L4 traffic between containers |
| Secondary | CAP_NET_RAW dropped | Cannot create raw sockets for L2 attacks |
| Tertiary | IP binding | Sessions bound to originating container IP |

**Why This Works:** Docker's ICC (Inter-Container Communication) setting blocks TCP/UDP between containers at the network layer. However, ICC doesn't block Layer 2 (Ethernet) traffic. Dropping CAP_NET_RAW prevents creation of raw sockets needed for ARP spoofing or packet crafting. See [Lateral Movement Scenario](credential-isolation.md#scenario-3-compromised-container-attempts-lateral-movement) in the credential isolation threat model for details.

---

### 6. Session Token Attacks

Attackers could attempt to steal session tokens and use them from other locations.

**Attack Vectors:**
- Reading session token from proxy internal socket
- Network sniffing to capture tokens in transit
- IP spoofing to bypass session IP binding
- Waiting for session reuse from different context

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | IP binding | Sessions only valid from originating container IP |
| Secondary | CAP_NET_RAW dropped | Cannot spoof source IP addresses |
| Tertiary | Explicit unregistration | Sessions removed on sandbox destroy; optional TTL if configured |
| Network | ICC=false | Cannot access other containers to reuse their sessions |

**Why This Works:** Even if an attacker obtains a session token, it's bound to the container's IP address. Using it from another location fails validation. IP spoofing would require CAP_NET_RAW, which is dropped. See [Session Token Scenario](credential-isolation.md#scenario-4-session-token-theft-and-reuse) in the credential isolation threat model for the complete analysis.

---

### 7. DNS Exfiltration

Data can be encoded in DNS queries and sent to attacker-controlled nameservers.

**Attack Vectors:**
- Encoding data in DNS subdomains: `secret-data.evil.com`
- Using DNS TXT records for bidirectional communication
- Leveraging DNS-over-HTTPS to bypass monitoring

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | DNS filter | All DNS routed through unified-proxy |
| Secondary | Domain allowlist | Only allowed domains resolve |
| Tertiary | Internal network | No direct access to external DNS servers |

**Why This Works:** The unified-proxy runs a DNS filter addon that intercepts all DNS queries. Only domains on the allowlist resolve. Attempts to query `evil.com` return NXDOMAIN. Direct access to external DNS (8.8.8.8) is blocked by network isolation. See [Network Isolation](network-isolation.md) for configuration.

---

### 8. Sudo Escalation

If the AI gains unrestricted sudo access, it could bypass container restrictions.

**Attack Vectors:**
- `sudo rm -rf /` - Delete everything writable
- `sudo chmod` - Modify file permissions
- `sudo apt-get remove` - Remove security tooling
- Installing malicious packages with elevated privileges

**Defense Layers:**

| Layer | Control | Effect |
|-------|---------|--------|
| Primary | Sudoers Allowlist | Only whitelisted commands permitted |
| Secondary | Read-only Filesystem | Even with sudo, can't write to read-only mounts |

**Why This Works:** The sudoers allowlist permits only safe commands (`sudo apt-get install *`, `sudo service * start/stop/restart/status`). There's no fallback `PASSWD:ALL` line. Even if a command were somehow permitted, the read-only filesystem would block destructive writes. See [Sudoers Allowlist](security-architecture.md#sudoers-allowlist) for the implementation.

---

## Why These Protections?

### Mistake Recovery Should Be Easy

When an AI makes a mistake (it will), recovery should be:
- **Obvious** - Clear error messages explain what happened
- **Reversible** - Worktrees can be destroyed and recreated
- **Contained** - Damage limited to the sandbox

### Defense in Depth

No single control is sufficient. Security comes from multiple overlapping layers:

1. **Credential Isolation** ensures secrets never enter the sandbox
2. **Network Isolation** blocks unauthorized egress even if code tries to exfiltrate
3. **Read-only Filesystem** prevents persistent damage even if commands execute
4. **Sudoers Allowlist** restricts privilege escalation even for allowed sudo use
5. **CAP_NET_RAW dropped** closes Layer 2 attack vectors that bypass higher-level controls

Each layer catches what others might miss. See [Security Architecture](security-architecture.md) for the complete model.

### Security Over Theater

The real security comes from the pillars (read-only filesystem, network isolation, credential isolation, sudoers allowlist).

---

## Explicit Non-Goals and Accepted Risks

The following are explicitly outside our threat model.

### Intentional Human Actions

If a human intentionally wants to destroy data, they can:
- `docker exec` into the container with root
- Remove the sandbox directory from the host
- Use operator approval to allow dangerous commands
- Modify `docker-compose.yml` to disable protections

**Rationale:** This isn't a security product protecting against malicious users. It's a safety net for AI-assisted development. The human operator is trusted.

### Container Escape Vulnerabilities

If an attacker exploits a Docker or kernel vulnerability to escape the container, sandbox protections don't help.

**Rationale:** We rely on Docker's security model. Container escape is an infrastructure-level concern outside this implementation's scope. Mitigation is through infrastructure patching and host hardening.

### Authorized Network Traffic

Sandboxes can send data to allowed services (GitHub, AI APIs). A compromised sandbox could:
- Push malicious code to an authorized repository
- Send data to AI APIs (within rate limits)
- Download packages that may be malicious

**Rationale:** Network isolation blocks unauthorized destinations. For authorized services, the trust model assumes the sandbox is doing legitimate development work. Repository-level protections (branch protection, code review) provide the next layer of defense.

### Disabled Credential Isolation

If credential isolation is disabled (`--no-isolate-credentials`), the AI can read real environment variables containing credentials.

**Rationale:** Credential isolation is opt-out for compatibility. When disabled, the user accepts that credentials may be exposed. See [Credential Isolation](credential-isolation.md) for why you should keep it enabled.

### Container Filesystem Write Capability

The sandbox container runs with `read_only: false` (required for DNS configuration and tmpfs setup).

**Mitigations:**
- Non-root user (uid 1000 via gosu) limits write scope
- Network isolation (`internal: true`) prevents data exfiltration
- Tmpfs `/home` means writes don't persist across restarts
- The worktree is mounted read-only; `/workspace/.git` is a tmpfs overlay

**Rationale:** Full read-only mode prevents DNS firewall setup via iptables. Shell override protections (e.g., custom shells) were removed as they were bypassable via `/bin/rm` or direct binary execution, making them security theater rather than a real control.

### NET_ADMIN Capability

The sandbox container is granted `CAP_NET_ADMIN` for iptables DNS firewall rules.

**Mitigations:**
- Privilege drop to uid 1000 via gosu occurs after iptables rules are set in `entrypoint-root.sh`
- After privilege drop, the unprivileged user cannot modify iptables rules (requires effective uid 0)
- `CAP_NET_RAW` is separately dropped to prevent raw socket abuse

**Rationale:** DNS firewall requires iptables. The capability is only useful during root-phase initialization.

### Mitmproxy CA Trust

The sandbox trusts the unified-proxy's mitmproxy CA certificate for HTTPS interception.

**Mitigations:**
- The CA private key never enters the sandbox (stored only on the proxy container)
- The sandbox only receives the public CA certificate (`mitmproxy-ca.pem`)
- Network isolation ensures only the unified-proxy can use this CA for interception

**Rationale:** Architectural requirement for credential injection. The proxy must terminate TLS to inject real credentials into outbound API requests.

### Proxy-Side Attack Surface

The unified-proxy runs `git unpack-objects` on untrusted pack data received from the sandbox during push operations. The proxy is the most privileged component in the architecture — it holds real credentials and has network access to GitHub.

**Risk:** A memory-safety bug in git's pack parser could allow code execution on the proxy host, potentially compromising all held credentials and bypassing network controls.

**Mitigations:**

| # | Control | Effect |
|---|---------|--------|
| 1 | Pack size bounded by `DEFAULT_MAX_PUSH_SIZE` (100MB) | Limits the attack surface to bounded input; rejects oversized payloads before parsing (`git_proxy.py:52`) |
| 2 | 10-second subprocess timeout | Kills hung or exploited git processes before they can establish persistence |
| 3 | Isolated temp directory | `unpack-objects` runs in a dedicated temporary object store, not the main bare repo |
| 4 | Minimal subprocess environment | Git subprocesses run with a stripped environment, reducing available attack primitives |
| 5 | Fail-closed on any error | Parse failures, timeouts, or unexpected exit codes all result in push rejection |

**Accepted risk rationale:** A pure-Python pack parser would be equally complex and less battle-tested against adversarial input than git's C implementation. Git's pack format handling has extensive fuzzing coverage upstream, and the mitigations above bound the blast radius of any exploit. The alternative — not inspecting pack data — would leave the restricted-paths check (`DEFAULT_RESTRICTED_PUSH_PATHS` in `git_policies.py:21`) unenforceable, which is a worse security outcome.

---

### In-Memory State Reset

Rate limiters, circuit breakers, and nonce replay stores reset on proxy restart.

**Mitigations:**
- Rate limiter reset only allows a brief burst before new limits take effect
- Nonce replay protection is bounded by the HMAC timestamp window (clock skew tolerance), so expired nonces are rejected regardless of store state
- Circuit breakers reset to closed (healthy) state, which is the safe default

**Rationale:** Persistence adds complexity without meaningful security gain given the bounded time windows.

---

## Design Principles

1. **Fail safe** - When in doubt, block the operation
2. **Prefer UX** - Friendly messages over cryptic errors
3. **Assume good intent** - Protect against accidents, not attacks
4. **Minimize friction** - Safe operations should "just work"
5. **Recoverability** - Destroying and recreating sandboxes is cheap

---

## Next Steps

- [Security Architecture](security-architecture.md) - Security pillars and defense layers
- [Credential Isolation](credential-isolation.md) - Credential isolation architecture and threat model
- [Network Isolation](network-isolation.md) - Network modes and configuration
- [Security Overview](index.md) - Security architecture quick reference
