Raw capture — raw/cco.md


url: https://github.com/nikvdp/cco date_fetched: 2026-07-05 backfilled: true


cco (Claude Condom if you're so inclined) provides essential protection while Claude Code is up close and personal with your system. It automatically selects the best available sandboxing method - using native OS sandboxing (sandbox-exec on macOS, bubblewrap on Linux) when available, or falling back to Docker as a barrier to keep Claude contained while keeping your real system safe.

UPDATE: cco can now sandbox OpenAI's codex as well! Just start it with cco codex to don your codex condom! (Opencode, Pi coding agent, and factory.ai's droid are supported as well.)

Running Claude Code with --dangerously-skip-permissions feels great - fast, responsive, no interruptions. But going in unprotected has risks: web search makes Claude vulnerable to prompt injections that could trick it into accessing files outside your project or running unexpected commands.

cco lets you have it both ways: all the pleasure of autonomous Claude, with a barrier between Claude and your machine's sensitive areas.

For more information about cco's security model, limitations, and threat analysis, see SECURITY.md.

curl -fsSL https://raw.githubusercontent.com/nikvdp/cco/master/install.sh | bashJust type cco where you would normally type claude and your protection is on!

cco "write a hello world script"
cco "help me refactor this code"

** cco gets out of your way.** It's designed to feel natural - like using Claude directly, just safer.

You should barely notice cco is there, except for that reassuring feeling of safety.

** cco runs Claude Code inside a sandboxed environment.** It automatically detects and uses the best available sandboxing method:

The result? Claude gets the --dangerously-skip-permissions experience it needs to be productive, while potential risks are contained within the sandbox.

There are several alternatives for running Claude Code in containers:

Here's why cco is the better choice for developers who want simplicity and seamless integration:

Feature cco claudebox devcontainer
Setup complexity One command install Multi-step setup, profiles VS Code + config files
IDE dependency None None VS Code required
Startup time Instant Slower (profile builds) Container rebuild delays
macOS Keychain Automatic Manual setup Manual setup
Terminal resizing Automatic (SIGWINCH passthrough) Unknown Limited
Configuration Zero config Profile management devcontainer.json
Development profiles None needed 15+ profiles Basic
Project isolation Basic Advanced (per-project images) Basic
Philosophy Invisible simplicity Feature-rich environment IDE integration

Choose devcontainer if you:

Choose claudebox if you:

Choose cco if you:

cco isn't trying to be a development environment - it's trying to be invisible protection that lets you use Claude Code exactly as intended, just safely.

l -fsSL https://raw.githubusercontent.com/nikvdp/cco/master/install.sh | bash git clone https://github.com/nikvdp/cco.git cd cco chmod +x cco sudo ln -s "$PWD/cco" /usr/local/bin/cco

Interactive session

cco

Direct commands

cco "analyze this codebase" cco --resume # Claude Code option passed through

Get help

cco --help

Force a specific sandbox backend

cco --backend native # Use native sandbox (sandbox-exec/bubblewrap) cco --backend docker # Use Docker sandbox cco --backend auto # Auto-detect (default)

Rebuild the protective layer (Docker mode only, also updates to latest Claude Code version)

cco --rebuild

Pull the latest pre-built image and automatically remove older cco image tags

cco --pull --clean-old-images

System information and status

cco --info

Shell access for inspecting the container environment

cco shell cco shell 'ls -la' # Run shell commands inside the cco container

Custom environment

cco --env API_KEY=sk-123

Additional apt packages

cco --packages terraform,kubectl

Auto-accept startup recovery prompts

cco --yes "review this repo"

Enable Docker access

cco --docker-socket

Run Docker sessions from a custom image

cco --backend docker --image my-cco-snapshot:good --persist

Reuse the default repo-scoped Docker container filesystem across runs

cco --persist

Select a specific persistent session name for this repo

cco --persist=frontend cco --persist data-migration

Attach directly to an existing Docker container by name or ID

cco --persist-container my-devbox cco --persist-container 4d2b9f8c1a6e

Update cco installation

cco self-update

Clean up containers

cco cleanup

Safe mode (native sandbox): hide $HOME for stronger isolation (experimental)

cco --safe

Share directories read-only or hide them

cco --add-dir ~/configs:ro cco --allow-readonly ~/.ssh cco --deny-path ~/Downloads

`cco` also honors Claude Code's local project settings file at `.claude/settings.local.json`. If that file contains an `additionalDirectories` array, those directories are mounted read/write the same way as `--add-dir PATH:rw`. `cco` parses that file with `python3` when available and falls back to `jq`; if neither tool exists it prints a warning and skips those extra mounts.

- `--docker-socket`(experimental): Binds the host Docker socket into the sandbox so Claude can control Docker on your machine. This defeats the isolation barrier—avoid unless you explicitly need host Docker access.
- `--image IMAGE`/- `--docker-image IMAGE`(Docker only): Runs- `cco`against a specific Docker image instead of the default managed- `cco:latest`image. This is useful if you- `docker commit`a known-good persistent container yourself and want later- `cco`runs to start from that image. With- `--pull`,- `cco`pulls the chosen image first.
- `--clean-old-images`(Docker only): After a successful pre-built image pull, remove older- `ghcr.io/nikvdp/cco:*`tags automatically. Without this flag,- `cco`asks before removing old managed image tags when it can prompt.
- `--force-docker-bridge-network`(Docker only): Force bridge networking instead of host networking. By default cco uses- `--network=host`when available (Linux, OrbStack). Use this if you need port isolation or want explicit- `-p`port forwarding.
- `--yes`/- `-y`: Auto-accept startup recovery prompts such as macOS Keychain unlock before- `cco`starts. OAuth maintenance is automatic when needed and is not controlled by- `--yes`.
- `--allow-oauth-refresh`(experimental): Gives the container write access to your Claude credentials so refreshed tokens sync back to the host. Malicious prompts could corrupt or replace those credentials.
- `--persist`(Docker only, opt-in): Reuses the default persistent container for the current repo instead of starting fresh each run.- `cco`starts it for the invocation and stops it again when the run ends.
- `--persist=NAME`or- `--persist NAME`: Selects a specific persistent session for the current repo so you can keep multiple reusable container filesystems side by side.
- `--persist-container TARGET`: Attaches to an existing Docker container by name or ID instead of using cco's managed session naming.
- Session names use letters, numbers, dot, underscore, or dash. If you want to keep using a subcommand like `shell`, use bare`--persist shell ...`exactly as before for the default session.
- Persistent sessions keep installed tools and temporary files around, so later runs reuse that container filesystem instead of starting from a clean slate. They preserve the container filesystem, not a permanently running background session.
- Repo-scoped persist sessions let sibling git worktrees target the same reusable container. `cco`will not automatically broaden mounts for later worktrees, so reuse fails clearly if the chosen container does not already expose the current path.

Use `--persist` or `--persist NAME` when you want `cco` to manage the session for a repo. Use `--persist-container TARGET` when you already know the exact container you want to attach to and want that choice to win over `cco`'s naming logic.
Use `--image IMAGE` when you want `cco` to run against a custom base image, for example after `docker commit <container> my-cco-snapshot:good`. Custom image overrides are not compatible with `--rebuild` or `--packages`, because those flags only make sense for the default `cco`-managed image path.

- `--safe`(native only, experimental):- **Provides stronger filesystem isolation**by hiding your entire- `$HOME`directory from Claude. Only the project directory and explicitly shared paths remain visible.- **Trade-off**: Increased security but may cause some tools to fail if they need access to configuration files in- `$HOME`. Use- `--allow-readonly`to selectively expose needed paths.
- `--allow-readonly PATH`: Share extra files or directories read-only inside the sandbox.
- `--deny-path PATH`: Deny read/list/write access to a path so it is fully inaccessible to Claude.

You can create exceptions within denied paths by combining `--deny-path` with `--allow-readonly` or `--add-dir`:

Deny all of /run but allow access to a specific socket

cco --deny-path /run --allow-readonly /run/user/1000/pipewire-0

Deny a config directory but allow a specific subdirectory

cco --deny-path ~/.config --add-dir ~/.config/myapp

**Precedence rules:**

- Allow paths that are subpaths of denied paths act as specific exceptions
- The denied parent remains inaccessible (no read/list/write) except for explicitly allowed subpaths
- Works consistently on both Linux (bubblewrap) and macOS (Seatbelt)

Arguments after `--` are passed directly to the underlying sandbox backend (Docker, bwrap, or sandbox-exec). This enables advanced configuration like port forwarding without `cco` needing explicit flags for every option:

Forward ports for development servers (Docker mode)

cco -- -p 3000:3000 # Forward port 3000 cco -- -p 3000:3000 -p 8080:8080 # Forward multiple ports cco -- -e "MESSAGE=hello world" # Set env with spaces

Shell quoting is preserved - each argument after `--` remains a separate argv element.

For persistent configuration, set `CCO_SANDBOX_ARGS_FILE` to point to a file containing sandbox backend arguments (one per line):

`export CCO_SANDBOX_ARGS_FILE=~/.config/cco/sandbox-args`Example file (`~/.config/cco/sandbox-args`):

Forward dev server port

-p 3000:3000

Forward database port

-p 5432:5432

Custom environment

-e DATABASE_URL=postgres://localhost/mydb

**Format rules:**

- One argument per line (flag and value on separate lines)
- Lines starting with `#`are comments
- Empty lines are ignored

`cco` acts as a wrapper - any options it doesn't recognize get passed directly to Claude Code:

These Claude Code options work normally

cco --resume cco --model claude-3-5-sonnet-20241022 "write tests" cco --no-clipboard "analyze this file" cco --permission-mode auto "work through this task"

Mix cco and Claude options

cco --env DEBUG=1 --resume # cco + Claude options

When you pass a Claude permission mode directly, `cco` leaves it alone instead of adding its default bypass flag. If your trusted Claude settings set `permissions.defaultMode` to `auto` and Auto Mode is not disabled, `cco` also lets that default apply.

You can use `cco` as a generic sandbox wrapper for any CLI, not just Claude. This is helpful when you want a tool to run with full autonomy inside a contained environment.

Run a shell inside the sandbox

cco --command "bash"

Run a custom program in the sandbox

cco --command "python3" - <<'PY' print("hello from inside cco") PY

With additional project directories mounted read/write

cco --add-dir ~/work/secrets --command "ripgrep TODO"

Notes:

- `--command`replaces the Claude invocation; all following args are passed to your command as-is.
- `cco`does not add Claude-specific flags to your command (e.g., it won’t append- `--dangerously-skip-permissions`).
- Native/Docker sandboxing still applies, so the command runs contained with only the mounted paths available.
- Combine with `--allow-readonly`/`--deny-path`/`--add-dir ...:ro`to tighten what your command can read or modify.

`cco codex` is a convenience for running OpenAI’s Codex CLI with maximum autonomy while keeping it contained by `cco`.

What it does:

- Sets the command to `codex --dangerously-bypass-approvals-and-sandbox`so`codex`won’t prompt for permissions and can act autonomously.
- Mounts `~/.codex`into the sandbox so`codex`can access it.
- Leaves containment to `cco`(native Seatbelt/bubblewrap or Docker), so`codex`cannot escape mounted paths even with bypass flags.

Important runtime note:

- `cco`executes tools as the mapped non-root container user with- `HOME`set to that user's home.
- Direct `docker exec`sessions as`root`are unsupported for normal auth/config behavior. If you must run commands manually, use`docker exec -u hostuser ...`(or`-u <HOST_UID>:<HOST_GID>`).

Examples:

Start Codex inside cco's sandbox

cco codex "build and run a small demo"

Equivalent manual form using --command (when ~/.codex exists)

cco --add-dir ~/.codex --command "codex --dangerously-bypass-approvals-and-sandbox" "build and run a small demo"

Add extra directories Codex should be able to access

cco --add-dir ~/.codex --command "codex --dangerously-bypass-approvals-and-sandbox" "analyze this project"

Security note: `--dangerously-bypass-approvals-and-sandbox` applies to Codex’s internal permission checks, not to `cco`. The `cco` sandbox still constrains filesystem access to your project and explicitly mounted paths. Network access remains unrestricted by design.

`cco auto <agent>` runs an agent in its native "ask me when it matters" mode instead of the default full-bypass mode:

- `cco auto claude`runs Claude with- `--permission-mode auto`instead of- `--dangerously-skip-permissions`.
- `cco auto codex`runs Codex with- `--ask-for-approval on-request`instead of- `--dangerously-bypass-approvals-and-sandbox`; because it no longer uses the bypass flag, Codex's own sandbox may also apply.
- For all other agents (`gemini`,`droid`,`opencode`,`pi`) it behaves exactly like running the agent normally.

In every case `cco`'s sandbox still applies. For Claude, auto mode only changes Claude's permission posture; for Codex, replacing the bypass flag also lets Codex enforce its own sandbox unless your Codex configuration or arguments say otherwise.

cco auto claude "refactor this module" cco auto codex "build and run the tests"

`cco` uses host-based networking so that MCP (Model Context Protocol) servers or other tools you may have running on localhost are accessible to `cco`.

- **OrbStack**: Native host networking support - MCP servers on localhost work automatically
- **Docker Desktop 4.34+**: Host networking available - enable in Settings → Resources → Network
- **Older Docker**: Uses- `host.docker.internal`bridge - may require MCP server reconfiguration

If you're using MCP servers with localhost addresses and they're not accessible, consider:

- Upgrading to Docker Desktop 4.34+ or switching to OrbStack
- Reconfiguring MCP servers to use `host.docker.internal`instead of`localhost`

**Important**: Stdio-based MCP servers need to be installed inside the container. `cco` cannot access stdio-based MCP servers that you have installed on your Mac/host system.

This is because stdio MCP servers run as separate processes that Claude Code launches directly, and the container can only see programs installed within it.

**If installable via apt:**

`cco --packages your-mcp-server-package "help me code"`**For custom installation, modify the Dockerfile:**

Clone or fork the cco repository

git clone https://github.com/nikvdp/cco.git cd cco

Edit the Dockerfile to add your MCP server installations

Add lines like these before the final ENTRYPOINT/CMD:

RUN apt-get update && apt-get install -y your-mcp-server

RUN pip install your-python-mcp-server

RUN npm install -g your-nodejs-mcp-server

Build your custom image

./cco --rebuild "help me with MCP server functionality"

`cco` passes through everything you need:

- `ANTHROPIC_API_KEY`- Direct access
- `CLAUDE_CODE_OAUTH_TOKEN`- Externally managed Claude OAuth token; skips local credential checks and startup refresh
- Terminal settings (`TERM`,`NO_COLOR`)
- Git configuration
- Locale and timezone
- Claude Code background tasks enabled by default (use `--disable-background-tasks`to turn off)

Use .env files

echo "DEBUG=1" > .env cco

Blank lines, comments, whitespace-prefixed comments, and non-assignment lines in `.env` are ignored so shell-style formatting does not break startup.

- **Sandbox backend**: ONE of the following:- **macOS**: sandbox-exec (built-in) OR Docker
- **Linux**: bubblewrap (bwrap) OR Docker

- **Claude Code**: Must be authenticated (run- `claude`and login)
- **Bash**: For the wrapper

`cco` automatically finds your Claude credentials:

- **Native sandbox mode**:- **macOS**: Direct Keychain access via sandbox-exec
- **Linux**: Direct file access via bubblewrap

- **Docker sandbox mode**:- **macOS**: Extracts from Keychain and mounts securely
- **Linux**: Mounts- `~/.claude/.credentials.json`or config directory

- **Environment**:- `ANTHROPIC_API_KEY`and- `CLAUDE_CODE_OAUTH_TOKEN`passed through in both modes

Understanding the filesystem isolation differences between sandbox modes:

| Sandbox Mode | Host Filesystem Visibility | Isolation Level | Performance | Use Case | 
|---|---|---|---|---|
| Docker | Only mounted paths | Strongest | Slower startup | Maximum security | 
| Native | Entire host (read-only) | Basic | Fastest startup | Development convenience | 
| Native + `--safe` | Only project + whitelisted | Strong | Fast startup | Security + performance balance (experimental) | 

**Key Points:**

- **Native sandbox (default)**: Exposes your entire host filesystem as read-only. Claude can read any file your user can read, but can only write to the project directory, Claude config/state paths, explicitly shared paths, and trusted git worktree metadata when needed.
- **Docker sandbox**: Only shows explicitly mounted paths. Claude cannot see or read files outside mounted directories.
- `--safe`flag- `$HOME`directory entirely while keeping fast native performance. May cause some tools to fail if they require access to dotfiles or configuration in- `$HOME`.

**Security recommendation**: Use Docker mode for maximum filesystem isolation, or try `--safe` with native sandboxing for better security with fast performance (experimental - may require `--allow-readonly` for some tools).

- **macOS**: Uses sandbox-exec (Seatbelt) for OS-level isolation
- **Linux**: Uses bubblewrap for lightweight containerization
- **Benefits**: Fast startup, no Docker overhead, direct system integration

- **Container specs**:- Node.js 20 with development tools
- Modern CLI utilities (jq, ripgrep, fzf)
- Multiple language support
- Database clients and network tools


- **Filesystem isolation**: Level depends on sandbox mode- **Docker mode**: Only project and explicitly mounted paths visible
- **Native mode (default)**: Entire host filesystem visible read-only; project, Claude config/state paths, explicitly shared paths, and trusted git worktree metadata are read/write
- **Native +**: Only project and whitelisted paths visible (stronger isolation)- `--safe`

- **Write protection**: All modes prevent writes outside project, Claude state, and explicitly whitelisted paths
- **Secure credential mounting**: Runtime-only credential access
- **Fresh session isolation**: Clean environment for each session
- **Terminal injection protection**: Linux sandbox blocks TIOCSTI/TIOCLINUX attacks via seccomp filtering

cd my-project cco cco "add tests to the auth module" cco --resume

export ANTHROPIC_API_KEY=sk-key cco "review this pull request"

**These features are experimental and may have edge cases. Use with caution.**

OAuth token sync-back (EXPERIMENTAL)

Lets sandboxed Claude sync refreshed credentials back to the host

cco --allow-oauth-refresh "help me code"

Credential management (EXPERIMENTAL)

Backup and restore Claude Code credentials for safety

cco backup-creds # Backup current credentials cco restore-creds # Restore from most recent backup cco restore-creds backup-file.json # Restore from specific backup

**OAuth refresh feature**: When the selected backend cannot safely persist an in-sandbox refresh, `cco` repairs already-expired Claude OAuth credentials with a fixed host-side Claude call before startup. If stored credentials are still valid but expire within two hours, `cco` starts Claude normally and runs the host-side refresh in the background. The experimental `--allow-oauth-refresh` mode is different: it enables bidirectional credential sync when sandboxed Claude refreshes expired tokens. It uses race condition protection and creates automatic backups.

**Credential management**: Provides manual backup/restore of Claude Code credentials with cross-platform support (macOS Keychain + Linux files).

**Authentication issues**

- Run `claude`first to authenticate
- Check Claude works outside `cco`
- On macOS over SSH, `cco`will offer to unlock your login keychain if it cannot read Claude credentials. Use`--yes`to auto-accept that recovery step.

**Token expiration**

- If you get authentication errors, your OAuth token may have expired or need a fresh browser login
- `cco`checks stored OAuth expiry before sandbox startup. When the selected backend cannot safely persist an in-sandbox refresh, already-expired credentials are repaired before Claude starts. Valid credentials that expire within two hours are refreshed in the background while startup continues.
- **Fallback**: Run- `claude`directly (outside- `cco`) to re-authenticate, then retry with- `cco`
- For in-sandbox refresh sync-back, the beta `--allow-oauth-refresh`flag will sync container credentials back to your host. Only use it if you accept the additional credential tampering risk.

**Docker problems**

- Start Docker daemon
- Verify with `docker info`

**Permission errors**

- `cco`handles user mapping automatically
- Try `cco --rebuild`if needed

**Experimental features not working**

- OAuth refresh (`--allow-oauth-refresh`) is beta, reduces credential isolation, and may have issues
- Fallback: authenticate directly with `claude`if the startup refresh cannot recover credentials
- Use credential backup/restore commands for safety: `cco backup-creds`/`cco restore-creds`

**Token expires during active session**
If Claude stops responding with API errors during an active `cco` session, your OAuth token has likely expired mid-session. `cco` normally refreshes valid tokens that expire within two hours in the background during startup, but a long-running session can still cross the expiry boundary.

**Root cause**: Some sandbox backends cannot safely persist Claude's refreshed OAuth credentials back to the host credential store without granting broader credential or home-directory write access. `cco` keeps the sandbox narrow, repairs already-expired credentials before startup, and performs background host-side refresh for valid credentials that are close to expiry.

**Workaround**:

- Open a new terminal window
- Run `claude`(outside`cco`)
- Run `/login`to re-authenticate
- Exit the raw claude session
- Quit your current `cco`session
- Restart with `cco --resume`to pick up the refreshed credentials

For repeated startup failures, run plain `claude` once to confirm the account can refresh or re-login outside the sandbox, then start `cco` again.

**Stdio-based MCP servers not available**
If Claude reports that stdio-based MCP servers are not found or not working, they need to be installed inside the container. See MCP Server Support section for installation instructions.

cco shell # Get inside for inspection cco --info # Check system status ``cco` provides protection primarily through filesystem isolation and credential management. Important limitations to understand:

Network access is intentionally unrestricted to support MCP servers and maintain Claude's full functionality. The primary security benefits come from filesystem and credential isolation, not network isolation.

For detailed security information, threat model, and limitations, see SECURITY.md.

Practice safe computing.

Pull requests welcome! Please maintain all safety mechanisms.

MIT License