Firewall & Security
Each cspace devcontainer runs an iptables-based egress firewall that restricts outbound network traffic to an explicit allowlist. This prevents autonomous agents from accessing arbitrary internet services — they can only reach GitHub, npm, Anthropic’s API, and domains you explicitly approve.
The firewall is implemented in lib/scripts/init-firewall.sh and runs during container startup.
Firewall model
Section titled “Firewall model”The firewall uses a default-deny policy with allowlisted exceptions:
- All outbound traffic is blocked unless it matches the allowlist
- Blocked connections get an immediate REJECT (not a silent DROP), so tools fail fast with a clear error rather than hanging on a timeout
- Inbound traffic is restricted to established connections and Docker network traffic
- Forwarding is disabled entirely
Initialization sequence
Section titled “Initialization sequence”The firewall script runs on every container start (iptables rules are kernel state and don’t persist across container restarts). The initialization follows a careful sequence to avoid locking the container out of the network:
- Extract Docker DNS rules — save internal Docker DNS resolution rules before flushing
- Set permissive policies — temporarily allow all traffic to prevent lockout during rule changes
- Flush all existing rules — clean slate across all iptables tables (filter, nat, mangle)
- Restore Docker DNS — re-apply the saved DNS resolution rules so container DNS keeps working
- Build the allowlist — resolve domains, fetch GitHub IP ranges, add CIDR blocks
- Set restrictive policies — switch to default-deny with the allowlist in place
- Verify — confirm blocked sites are blocked and allowed sites are reachable
What’s allowed
Section titled “What’s allowed”Always allowed (base services)
Section titled “Always allowed (base services)”These are required for Claude Code and core tooling to function:
| Category | Domains |
|---|---|
| DNS | UDP port 53 (all DNS resolution) |
| SSH | TCP port 22 (git operations over SSH) |
| Localhost | Loopback interface (internal communication) |
| npm | registry.npmjs.org |
| Anthropic | api.anthropic.com, auth.anthropic.com |
| Claude | claude.ai, context7.com |
| Observability | sentry.io, us.sentry.io, mcp.sentry.dev, statsig.anthropic.com, statsig.com |
| Testing | playwright.dev |
| Vercel CDN | 76.76.21.0/24, 64.29.17.0/24, 216.198.79.0/24 |
GitHub IP ranges
Section titled “GitHub IP ranges”GitHub IPs are fetched dynamically from the GitHub meta API (https://api.github.com/meta) and include the web, api, and git CIDR ranges. This covers:
github.comweb interface- GitHub API (
api.github.com) - Git operations over HTTPS
- GitHub Actions artifact storage
The script validates the API response before adding ranges. If the fetch fails, the firewall script exits with an error — a firewall without GitHub access is not useful for agents.
Docker network traffic
Section titled “Docker network traffic”The firewall auto-detects all connected Docker subnets and allows traffic to and from them. This ensures:
- Services within the instance (postgres, redis, etc.) are reachable
- The host gateway network is accessible
- Browser sidecars (Playwright, Chromium CDP) can communicate with the devcontainer
Project-specific domains
Section titled “Project-specific domains”Additional domains are read from the CSPACE_FIREWALL_DOMAINS environment variable (space-separated). This variable is set from the firewall.domains array in .cspace.json:
{ "firewall": { "domains": ["api.example.com", "cdn.example.com"] }}Domains are resolved to IP addresses via DNS at firewall initialization time. If a domain fails to resolve, a warning is logged but the firewall continues.
Domain resolution
Section titled “Domain resolution”The firewall uses ipset with hash:net to build a CIDR-based allowlist. Domains are resolved using two methods:
- CIDR ranges (GitHub IPs, Vercel CDN) — added directly to the ipset
- Domain names — resolved to IP addresses via
dig +short A, then each IP is added individually
This means the allowlist is IP-based, not hostname-based.
Verification
Section titled “Verification”After building the allowlist, the firewall script runs two verification checks:
- Blocked test — confirms
https://example.comis unreachable - Allowed test — confirms
https://api.github.com/zenis accessible
If either check fails, the script exits with an error and the container will not start successfully. A marker file (/tmp/.firewall-init-done) is created on success.
Customizing the firewall
Section titled “Customizing the firewall”Adding project domains
Section titled “Adding project domains”The most common customization is adding domains your project needs (APIs, CDNs, package registries):
{ "firewall": { "domains": [ "api.stripe.com", "fonts.googleapis.com", "registry.yarnpkg.com" ] }}Disabling the firewall
Section titled “Disabling the firewall”If you don’t want network restrictions (e.g., during initial setup or debugging):
{ "firewall": { "enabled": false }}Overriding the firewall script
Section titled “Overriding the firewall script”For advanced customization, you can override the entire firewall script by placing a custom version at .cspace/ and updating the Dockerfile. Since the firewall script is copied into the container image at build time, overriding it requires a custom Dockerfile:
- Place your custom firewall script at
.cspace/init-firewall.sh - Create a
.cspace/Dockerfilethat extends the base and copies your script:
FROM cspace-devCOPY .cspace/init-firewall.sh /usr/local/bin/init-firewall.shRUN chmod +x /usr/local/bin/init-firewall.shSecurity model
Section titled “Security model”Container isolation
Section titled “Container isolation”Each devcontainer instance is isolated from others:
- Separate filesystem — each instance has its own workspace volume
- Separate network — each instance runs on its own Docker network
- Separate credentials — each instance has its own GitHub CLI config
- No cross-instance access — instances cannot reach each other’s services
The only shared state is the agent memory and logs volumes, which are read/write for all instances.
Authentication
Section titled “Authentication”- GitHub token — passed via
GH_TOKENin the project.envfile, loaded into the container environment - No SSH agent access — containers use HTTPS for all git operations
- Anthropic API key — passed through the environment for Claude Code
Capabilities
Section titled “Capabilities”The devcontainer requests two Linux capabilities:
CAP_NET_ADMIN— required foriptablesto configure the firewallCAP_NET_RAW— required for raw socket operations used by the firewall
No other elevated capabilities are granted. The dev user has passwordless sudo for convenience during development, but the container runs as a non-root user by default.