---
title: "Security"
description: "Container security controls: firewall overview, Docker socket access, Linux capabilities, host proxy, and credential forwarding"
icon: "shield"
---

Clawker runs every agent in an isolated Docker container with a default-on network firewall. This guide explains the container security controls and how to customize them.

## Network Firewall

The firewall is **deny-by-default**. When enabled, it blocks **all outbound traffic** — DNS queries for unlisted domains return NXDOMAIN, and TLS connections to unlisted destinations are reset. Only domains you explicitly allowlist are reachable.

This is intentional. The hardcoded allowlist is kept to the bare minimum required for Claude Code itself to function. Everything else — including GitHub, npm, PyPI, or any other service your project needs — must be explicitly configured by you.

See the [Firewall](/firewall) guide for the full architecture, configuration, CLI commands, and troubleshooting.

### Hardcoded Allowed Domains

These are the **only** domains allowed by default — they are required for Claude Code to function (API access, OAuth, telemetry):

| Domain | Purpose |
|--------|---------|
| `api.anthropic.com` | Claude API |
| `platform.claude.com` | OAuth token exchange |
| `.claude.ai` | OAuth authorization, downloads |
| `mcp-proxy.anthropic.com` | MCP tool server proxy |
| `sentry.io` | Error tracking |
| `statsig.anthropic.com` | Feature flags |
| `statsig.com` | Feature flags |
| `.datadoghq.com` | Telemetry (all Datadog subdomains/regions) |
| `.datadoghq.eu` | Telemetry (Datadog EU regions) |

<Warning>
**GitHub, npm, PyPI, and all other services are NOT hardcoded.** The generated `clawker.yaml` template includes `github.com` and `api.github.com` as a convenience starting point, but these are project-level config entries — not system defaults. If you remove them from your config or start from scratch, GitHub will be blocked.

You must explicitly add every domain your agent needs to reach via `add_domains` in your `.clawker.yaml` or `clawker firewall add <domain>` at runtime. See [Firewall Configuration](/firewall#configuration) for details.
</Warning>

### Disabling the Firewall

The global firewall toggle lives in `settings.yaml` (not the project config):

```yaml
# ~/.config/clawker/settings.yaml
firewall:
  enable: false
```

<Warning>
Disabling the firewall removes **all** outbound network restrictions. The agent can reach any endpoint on the internet. Only do this in trusted environments where you accept the risk of unrestricted egress.
</Warning>

## Agent Awareness Prompt

Every Clawker container image includes an agent prompt file at `/etc/claude-code/CLAUDE.md`. This file is automatically loaded by Claude Code and gives the agent awareness of its containerized environment:

- What it can and cannot do (filesystem, network, Docker)
- How to diagnose firewall blocks (NXDOMAIN, connection resets, certificate errors)
- What `clawker firewall` commands the user can run on the host to unblock domains
- Environment variables available for troubleshooting (`CLAWKER_PROJECT`, `CLAWKER_AGENT`, `CLAWKER_FIREWALL_ENABLED`, etc.)

This means when the agent hits a blocked domain, it can explain the problem and suggest the right `clawker firewall add` command — without the user needing to know the details.

## Container PID 1 and Privilege Drop

The container's PID 1 is `clawkerd`, the per-container daemon that supervises Claude Code. clawkerd runs as root inside the container, but it never executes user-controlled code in its own process. The user CMD (Claude Code by default) is forked with kernel-side privilege drop via `SysProcAttr.Credential` — between `fork()` and `execve()` the kernel performs `setgroups → setgid → setuid` atomically, so there is no user-mode code path between root and the unprivileged `claude` user (UID baked at build time — host-derived on Linux for bind-mount writability, 1001 elsewhere).

The clawker control plane talks to clawkerd over a CN-pinned mTLS Session — only a peer holding a cert with CN `clawker-controlplane` chained to the clawker CA can dispatch commands. See [Container Internals → clawkerd](/container-internals#clawkerd-the-containers-pid-1) for the full PID-1 contract.

## Docker Socket

By default, the Docker socket is **not** mounted into containers:

```yaml
security:
  docker_socket: false
```

Enabling `docker_socket: true` mounts `/var/run/docker.sock` read-write into the container. This gives the agent full control over your Docker daemon — it can create, modify, and delete any container, image, or volume on your host. Only enable this if your workflow genuinely requires Docker-in-Docker.

## Linux Capabilities

Agent containers require no special privileges for firewall enforcement. eBPF cgroup programs are attached from outside the container by the clawker control plane — raw socket creation is blocked by the eBPF `sock_create` program rather than by container capabilities.

The supporting infrastructure containers use minimal capabilities. The clawker control plane (`clawker-controlplane`) runs with `CAP_BPF` + `CAP_SYS_ADMIN` to load, attach, and pin eBPF programs and maps. The custom CoreDNS build runs with `CAP_BPF` + `CAP_SYS_ADMIN` to open the pinned `dns_cache` BPF map and update it on DNS responses. Both mount only `/sys/fs/bpf` — no broader host access. See [DNS Cache Population](#dns-cache-population) below for why CoreDNS needs these capabilities.

Agent containers default to **no extra capabilities** (`cap_add: []`). The `cap_add` field is available if your workflow needs additional Linux capabilities:

```yaml
security:
  cap_add: []  # default — agent containers are fully unprivileged
  # cap_add:
  #   - SYS_PTRACE   # only add capabilities your workflow actually needs
```

### IPv6 Support

Clawker's firewall is IPv4-first. There are two cases to understand:

- **Dual-stack sockets (IPv4-mapped IPv6, `::ffff:x.x.x.x`).** Most modern programs — `ssh`, `curl`, Node.js, Python, Go — open dual-stack IPv6 sockets by default and connect to IPv4 targets via IPv4-mapped addresses. The eBPF `cgroup/connect6` program detects these and applies the **same full routing logic as IPv4**: DNS redirect to CoreDNS, gateway lockdown, per-domain route lookup via the DNS cache, and Envoy fallback for unknown destinations. From the user's perspective, dual-stack traffic is enforced identically to pure IPv4.
- **Native IPv6.** Connections to real IPv6 addresses (e.g., `2606:4700::1` or anything forced via `-6`) are **denied**. The egress stack (Envoy listeners, CoreDNS forward zones, eBPF DNS cache) is IPv4-only, and there is no safe way to transparently route native IPv6 traffic today. If your workflow requires IPv6 connectivity, prefer hostnames so the container resolves an A record, or open an issue describing the use case.


### DNS Cache Population

Per-domain TCP routing in eBPF uses a `dns_cache` BPF map keyed by destination IP. When a container connects to `140.82.121.4`, the cgroup hook looks up the IP in the map, recovers the domain hash, and checks the global `route_map` for a matching `{domain_hash, dst_port}` rule. This only works if the cache is kept consistent with live DNS responses.

Clawker ships a **custom CoreDNS build** with an in-tree `dnsbpf` plugin (`internal/dnsbpf/`). The plugin intercepts every DNS response for an allowlisted zone, extracts the A records, and writes each `(ip → domain_hash, ttl)` entry to the pinned `dns_cache` map in real time. This replaces an earlier startup-time seeding approach that was vulnerable to DNS round-robin rotation: seeded IPs would go stale as CDNs cycled backends, silently blackholing traffic until the firewall was reloaded. The plugin-based approach tracks whatever the upstream resolver returns on every query, so any IP a container actually uses has been observed and hashed at the moment of connection.

The plugin only writes to the BPF map on successful A-record answers. NXDOMAIN and other non-success responses are passed through untouched, so blocked domains never leak into the cache. The custom CoreDNS image is built from a SHA-pinned `alpine:3.21` base with the CoreDNS binary copied in — no shell, no package manager, no additional utilities. Its elevated capabilities (`CAP_BPF` + `CAP_SYS_ADMIN`) are scoped to the minimum needed for `bpf(BPF_OBJ_GET)` and `bpf(BPF_MAP_UPDATE_ELEM)` on the pinned map, and the only host access is a bind mount of `/sys/fs/bpf`.

### TCP/SSH Port-Level Routing

Raw TCP and SSH protocols have no domain-level metadata (no SNI equivalent), so Clawker cannot inspect the intended destination. Instead, eBPF routes **all** outbound traffic on the configured port to Envoy, which forwards it to the whitelisted domain. For example, a `proto: ssh` rule for `github.com` on port 22 means every port 22 connection from the container goes to GitHub, regardless of the destination the agent specified. An attacker cannot reach arbitrary SSH servers — the traffic is locked to the whitelisted target.

This means only one domain can be whitelisted per TCP/SSH port — a [tracked limitation](https://github.com/schmitthub/clawker/issues/235), deferred for after the control plane work. See the [Firewall guide](/firewall#protocol-behavior) for details.

## Host Proxy

The host proxy is a lightweight daemon that runs on your host machine and forwards credentials into containers. It's enabled by default:

```yaml
security:
  enable_host_proxy: true
```

The host proxy is required for Git HTTPS credential forwarding and browser-based OAuth flows (e.g., `gh auth login`). See the [Credential Forwarding](/credentials) guide for details.

## Troubleshooting

For firewall troubleshooting, see the [Firewall guide](/firewall#troubleshooting).
