Raised: $0
0% of monthly goal Help us cross the finish line!
Goal: $12,000
Raised: $0 Goal: $12,000
0% of monthly goal Help us cross the finish line!
Sponsor DDEV

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.

ddev-claude

A DDEV add-on that adds a sandboxed sidecar container for running Claude Code (Anthropic’s AI coding CLI) with --dangerously-skip-permissions (YOLO mode) safely contained behind an iptables + ipset + dnsmasq firewall.

What you get

Why this exists

Running AI coding agents autonomously is productive — until the agent hallucinates a curl | sh against a compromised server, or an adversarial prompt talks it into exfiltrating your .env file. This add-on removes those risks by constraining the agent’s network reach at the kernel level (iptables default-DROP) while still letting it fetch dependencies from the usual package registries.

With the firewall active, --dangerously-skip-permissions becomes safe enough for routine use: the worst the agent can do is corrupt your working tree, and git reset --hard recovers from that.

Installation

ddev add-on get makraz/ddev-claude
ddev restart

Or from a local checkout (development):

ddev add-on get /path/to/ddev-claude
ddev restart

Usage

# Export your Anthropic API key (or use OAuth login on first run)
export ANTHROPIC_API_KEY=sk-ant-...

# Optional: GitHub auth for the gh CLI and the GitHub MCP
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...

# Start / restart the DDEV stack (first build takes ~5 minutes)
ddev restart

# Launch Claude Code in YOLO mode inside the sandbox
ddev claude

# Opt out of YOLO for a single run
CLAUDE_SAFE=1 ddev claude

# Pass extra flags through to the claude CLI
ddev claude --resume
ddev claude --help

Allowing additional outbound domains

Set EXTRA_ALLOWED_DOMAINS in your host shell (space-separated) before ddev start / ddev restart:

export EXTRA_ALLOWED_DOMAINS="sentry.io api.stripe.com"
ddev restart
ddev claude

The firewall’s dnsmasq resolves these on demand and adds the resulting IPs to the allow-list ipset. You can also persist them inside the container at /etc/firewall/extra-domains.list (one per line).

Verifying the sandbox

After ddev claude starts, run a smoke test inside the session:

Please run:
  curl -sS --max-time 5 https://api.github.com           # should succeed
  curl -sS --max-time 3 https://example.com || echo BLOCKED  # should be BLOCKED
  sudo iptables -L OUTPUT -n | head -1                   # should say "policy DROP"

If example.com is reachable or the iptables policy is ACCEPT, the firewall is not active — stop and investigate before trusting the agent with autonomous work.

Architecture

┌─ Host ───────────────────────────────────────────────────────────────┐
│                                                                      │
│   ddev claude ──► docker exec ──► ┌─ claude sidecar ──────────────┐  │
│                                   │  Claude Code CLI              │  │
│                                   │  PHP 8.5 / Composer           │  │
│                                   │  Playwright / Chromium        │  │
│                                   │                               │  │
│                                   │  iptables default DROP        │  │
│                                   │  ipset allowed-ipv4 (dynamic) │  │
│                                   │  ipset allowed-net (ddev net) │  │
│                                   │  dnsmasq → ipset bridge       │  │
│                                   │                               │  │
│                                   │  mounts: /var/www/html (rw)   │  │
│                                   │          /home/claude/.claude │  │
│                                   └──────────┬────────────────────┘  │
│                                              │ ddev default network  │
│              ┌───────────────────────────────┼──────────────────┐    │
│              ▼                               ▼                  ▼    │
│        ┌─ web ──┐                    ┌─ db ────┐         ┌─ other─┐  │
│        │ nginx  │                    │ mariadb │         │  ddev  │  │
│        │ php-fpm│                    │         │         │svcs... │  │
│        └────────┘                    └─────────┘         └────────┘  │
└──────────────────────────────────────────────────────────────────────┘

How the firewall works

init-firewall.sh (run as root via NOPASSWD sudoers entry) sets up:

  1. ipsetsallowed-ipv4 (hash:ip) and allowed-net (hash:net).
  2. Initial DNS resolutiondig resolves the default + extra domains, populating allowed-ipv4.
  3. dnsmasq — listens on 127.0.0.1, upstreams to 127.0.0.11 (Docker’s embedded DNS — keeps DDEV service names like web/db resolvable), 1.1.1.1, 8.8.8.8. Each allow-listed domain is bound via ipset=/<domain>/allowed-ipv4, so any future resolution automatically extends the allow-list — handles CDN IP rotation.
  4. iptables — default policy DROP on INPUT/OUTPUT/FORWARD. ACCEPT only loopback, established/related, DNS (port 53), the two ipsets, the host gateway, and inbound 80/443.
  5. IPv6 — dropped entirely.
  6. Smoke testscurl reachability checks for github + npm + packagist (must succeed) and example.com (must fail).

Known limitations

Uninstall

ddev add-on remove claude
ddev restart

The named volumes claude-config and claude-history are preserved so that reinstalling the add-on later keeps your Claude Code auth. To fully wipe them:

docker volume rm ddev-<project>_claude-config ddev-<project>_claude-history

License

MIT — see LICENSE.

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.