Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.clawker.dev/llms.txt

Use this file to discover all available pages before exploring further.

Clawker ships a deny-by-default network firewall that restricts all container egress to explicitly allowed domains. The firewall runs as a shared stack — an Envoy egress proxy, a custom CoreDNS resolver with a dnsbpf plugin, and a set of eBPF cgroup programs — managed automatically by clawker on an isolated Docker bridge network. One firewall stack serves all clawker-managed containers on the host (1:N). It provides DNS-level blocking, per-domain TCP routing, and TLS-level inspection without granting your agent containers any special privileges.

Architecture

The firewall is composed of two managed Docker containers plus a set of eBPF programs attached from outside each agent container:
  • Envoy (envoyproxy/envoy, TLS listener 10000, sequential TCP listeners from 10001) — TLS termination with per-domain certificates for all allowed domains. Envoy terminates TLS, inspects HTTP traffic (paths, methods, response codes visible), then re-encrypts upstream. Default deny (connection reset) for unrecognized SNI.
  • CoreDNS (clawker-coredns:latest, port 53) — a custom CoreDNS build from cmd/coredns-clawker. Provides deny-by-default DNS filtering (NXDOMAIN for anything not in the allowlist, Cloudflare malware-blocking upstream 1.1.1.2 / 1.0.0.2) and embeds the first-party dnsbpf plugin that writes every resolved IP to the BPF dns_cache map in real time — this is what lets the BPF connect4/connect6 programs do per-domain TCP routing. Runs with CAP_BPF + CAP_SYS_ADMIN and a /sys/fs/bpf bind mount, the minimum required to update the pinned cache map.
  • eBPF cgroup programs (connect4, sendmsg4, recvmsg4, connect6, sendmsg6, recvmsg6, sock_create) — loaded and attached from outside the agent container by the clawker control plane. Owns the pinned BPF maps under /sys/fs/bpf/clawker/ (container_map, route_map, dns_cache, bypass_map, metrics_map). The agent container itself runs fully unprivileged — no Linux capabilities, no firewall scripts, no init-time network gymnastics.
Both managed containers run on the clawker-net bridge network with deterministic static IPs computed from the network gateway by replacing its last octet: Envoy at <network>.200, CoreDNS at <network>.201 (so e.g. 192.168.215.200 / .201 on a default Docker bridge with gateway 192.168.215.1). Agent containers join the same network with --dns pointed at CoreDNS, and the eBPF connect4/connect6 programs redirect all outbound TCP to Envoy. CoreDNS forwards Docker internal names (host.docker.internal, monitoring stack containers) back to Docker’s embedded DNS at 127.0.0.11 so internal networking keeps working.
Agent Container      CoreDNS (.201)     eBPF maps       Envoy (.200)      Internet
    |                      |                |                |               |
    |-- DNS query -------->|                |                |               |
    |                      |-- dnsbpf write IP->hash ------->|               |
    |<- NXDOMAIN (blocked)-|                                                  |
    |<- resolved IP -------|                                                  |
    |                                                                          |
    |-- TCP (connect4/connect6 rewrite via route_map + dns_cache) --->|       |
    |                                                        |-- TLS inspect ->|
    |                                                        |<- response ----|
    |<-------------------------------------------------------|               |

How It Works

  1. When you run clawker run or clawker container create with the firewall enabled, clawker brings the firewall stack up automatically (this happens transparently — see Control Plane if you want the lifecycle details).
  2. The clawker-net bridge network is created, the eBPF programs are loaded into the kernel and their maps pinned under /sys/fs/bpf/clawker/, then CoreDNS and Envoy are launched. CoreDNS opens the pinned dns_cache map on startup, so the eBPF state must exist before CoreDNS boots — this ordering is preserved on every reload.
  3. Project rules from .clawker.yaml are merged with system-required rules (additive merge, dedup by destination:protocol:port).
  4. Envoy, CoreDNS, and the global route_map are (re)generated from the merged ruleset.
  5. Agent containers join the firewall network; the eBPF cgroup programs attach to each container’s cgroup, and an entry is written to container_map (cgroup_id → container config). Presence in container_map is what gates enforcement — the route_map itself is global.
  6. As the agent resolves DNS, the dnsbpf plugin writes IP → {domain_hash, TTL} entries into the dns_cache map. On each outbound TCP connection, the connect4 / connect6 programs look up the destination IP in dns_cache, then look up {domain_hash, dst_port} in route_map to decide which Envoy listener to redirect to.
  7. Envoy and CoreDNS are health-probed continuously. When the last clawker-managed agent container exits, the firewall stack and eBPF state are drained and flushed cleanly.
Because route_map is global, clawker firewall add, clawker firewall remove, and clawker firewall reload immediately propagate rule changes to all running agent containers via an atomic route-map sync — no agent restart, no firewall stack restart.

IPv4, IPv6, and dual-stack

The BPF connect6 program applies the full connect4 routing logic to IPv4-mapped IPv6 addresses (::ffff:x.x.x.x). Dual-stack clients — SSH, curl, Node.js, Go’s default resolver — use IPv4-mapped sockets, so they get the same per-domain routing through Envoy as native IPv4.
Native IPv6 is denied. The firewall does not currently route native IPv6 egress (only loopback ::1 and IPv4-mapped addresses pass through). If your agent truly needs native IPv6 to reach a service, it will fail with a connection reset. In practice this is rare because dual-stack clients fall back to IPv4-mapped automatically.

Default Allowed Domains

The hardcoded allowlist is deliberately bare-bones — only domains required for Claude Code itself are included (API access, OAuth, telemetry):
DomainPurpose
api.anthropic.comClaude API
platform.claude.comOAuth token exchange
.claude.aiOAuth authorization, downloads
mcp-proxy.anthropic.comMCP tool server proxy
sentry.ioError tracking
statsig.anthropic.comFeature flags
statsig.comFeature flags
.datadoghq.comTelemetry (all Datadog subdomains/regions)
.datadoghq.euTelemetry (Datadog EU regions)
A leading dot (e.g., .datadoghq.com) is the wildcard convention — it matches the apex domain and all subdomains. Use this for services with region-specific subdomains. Without the leading dot, only the exact domain is allowed.
GitHub, npm, PyPI, and all other services are NOT in this list. When you run clawker project init, 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 start from a blank config, GitHub SSH and HTTPS will be blocked.You must explicitly configure every external domain your agent needs. This is by design — an AI agent should not have unrestricted internet access. Review your project’s network dependencies and add them to add_domains or rules in your .clawker.yaml.

Common Setups

Here are examples for common services your agent will likely need. None of these are included by default — add what you need to your .clawker.yaml.

Git SSH (GitHub, GitLab, Bitbucket)

SSH git operations (git clone [email protected]:...) require an explicit SSH rule. The add_domains shorthand only covers HTTPS (port 443) — SSH needs a dedicated rules entry on port 22:
security:
  firewall:
    add_domains:
      - github.com       # HTTPS git + API
      - api.github.com   # GitHub API (needed for gh CLI)
    rules:
      - dst: github.com
        proto: ssh
        port: 22
        action: allow
Without the SSH rule, git push and git clone over SSH will fail with a connection reset. The agent’s SSH keys are forwarded from your host (see Credential Forwarding), but the firewall still needs to allow the traffic through. For GitLab or Bitbucket, add equivalent rules:
security:
  firewall:
    add_domains:
      - gitlab.com
      - bitbucket.org
    rules:
      - dst: gitlab.com
        proto: ssh
        port: 22
        action: allow
      - dst: bitbucket.org
        proto: ssh
        port: 22
        action: allow

Package Registries

security:
  firewall:
    add_domains:
      # Node.js / npm
      - registry.npmjs.org
      - registry.yarnpkg.com
      # Python / pip
      - pypi.org
      - files.pythonhosted.org
      # Go modules
      - proxy.golang.org
      - sum.golang.org
      - storage.googleapis.com
      # Rust / cargo
      - crates.io
      - static.crates.io

Full Working Example

A typical project that uses GitHub and npm:
security:
  firewall:
    add_domains:
      - github.com
      - api.github.com
      - registry.npmjs.org
    rules:
      - dst: github.com
        proto: ssh
        port: 22
        action: allow
The clawker project init template pre-populates GitHub domains and SSH rules for you. If you’re starting from the template, Git should work out of the box. These examples are for understanding what’s required if you’re building a config from scratch or adding new services.

Configuration

The firewall is configured in two places:
  • Global toggle: firewall.enable in settings.yaml (enable/disable the entire firewall)
  • Per-project rules: security.firewall section of .clawker.yaml (which domains to allow)
Project-level firewall configuration in .clawker.yaml:
security:
  firewall:
    add_domains:
      - "api.openai.com"
      - "registry.npmjs.org"
    rules:
      - dst: "api.example.com"
        proto: tls
        port: 443
        action: allow
        path_rules:
          - path: "/v1/chat/*"
            action: allow
        path_default: deny

add_domains

A convenience shorthand for allowlisting entire domains. Each entry is automatically converted to a TLS allow rule on port 443 with no path restrictions (allow-all routing through TLS inspection).
security:
  firewall:
    add_domains:
      - "api.openai.com"
      - "registry.npmjs.org"
      - "pypi.org"
      - ".datadoghq.com"   # Leading dot = wildcard (all subdomains)
A leading dot (e.g., .datadoghq.com) enables wildcard matching — the apex domain and all subdomains are allowed. Without the leading dot, only the exact domain is matched. Use wildcards for services with region-specific subdomains (e.g., Datadog’s us5.datadoghq.com, eu1.datadoghq.com).

rules

Full rule specification for fine-grained control:
FieldTypeDescription
dststringDomain name or IP address. Prefix with . for wildcard (e.g., .datadoghq.com matches all subdomains)
protostringtls, http, tcp, or ssh
portintDestination port
actionstringallow or deny (default: allow)
path_ruleslistOptional path prefix rules (each entry has path and action). Supported on tls and http protocols
path_defaultstringDefault action for paths not matching any path_rules entry (default: deny)

Protocol behavior

  • tls (default) --- HTTPS traffic. Envoy terminates TLS with a per-domain certificate, inspects HTTP traffic (paths visible in access logs), then re-encrypts upstream. With path_rules, per-path routing is applied; without path_rules, all traffic to the domain is allowed.
  • http --- Plain HTTP traffic. Envoy inspects the Host header for domain matching and applies path rules directly. No TLS involved.
  • tcp --- Raw TCP forwarding to a specific port. No domain or path inspection. See the port-level routing note below.
  • ssh --- SSH traffic forwarding. Functionally identical to tcp but semantically distinct. See the port-level routing note below.
TCP/SSH rules capture all traffic on the port, not just traffic to the whitelisted domain. TLS and HTTP protocols carry domain metadata (SNI and Host header) that Envoy uses to match traffic to the correct rule. Raw TCP and SSH have no such mechanism. Resolving the domain to an IP and writing per-IP routing rules is not viable — IPs change frequently for large services (CDN edge rotations, load balancer failovers, anycast shifts), making IP-based rules brittle. Instead, clawker creates a single eBPF routing rule per destination port that redirects all traffic on that port to a dedicated Envoy TCP listener. Envoy resolves the whitelisted domain at connection time and forwards traffic there.For example, a proto: ssh rule for github.com on port 22 means every outbound port 22 connection from the container — regardless of what IP or hostname the agent specifies — ends up at GitHub’s SSH endpoint. The agent cannot reach any other SSH server. This is more restrictive than it might appear: it’s not that arbitrary port 22 traffic is allowed through, it’s that all port 22 traffic is funneled to a single destination.If multiple rules target the same port (e.g., SSH rules for both github.com and gitlab.com on port 22), the first rule in the egress rules list wins — the eBPF program evaluates rules in order and the first port match captures all traffic for that port. The second rule’s Envoy listener would exist but never receive traffic. For this reason, only one domain can effectively be whitelisted per TCP/SSH port. If you need SSH access to both GitHub and GitLab, configure one provider to use HTTPS git instead, or use a non-standard SSH port if the provider supports it.
Multi-domain support for TCP/SSH ports is a tracked limitation, deferred for after the control plane work. In the meantime, HTTPS git works for most multi-provider setups.
For security testers: a successful connect() on a port with a whitelisted rule does not mean you have reached your intended target. The connection was silently redirected to the whitelisted service. Always verify the remote banner or certificate before concluding you have egress — otherwise you may be sending data to GitHub, GitLab, or another corporate service rather than your C2.

Path rules

Path rules give you fine-grained control over which URL paths are allowed for a domain. Envoy uses prefix matching --- a rule for /api/v1 matches /api/v1, /api/v1/users, /api/v1/models/list, etc. When path_rules is specified, path_default controls what happens to paths that don’t match any rule. It defaults to deny, meaning only explicitly allowed paths get through. Pattern 1: Allow specific paths, deny everything else (default deny)
security:
  firewall:
    rules:
      - dst: "api.example.com"
        proto: tls
        port: 443
        action: allow
        path_rules:
          - path: "/v1/chat"
            action: allow
          - path: "/v1/models"
            action: allow
        path_default: deny  # this is the default, shown for clarity
Requests to /v1/chat/completions and /v1/models are forwarded. Everything else (e.g., /admin, /internal) gets a 403 Blocked by clawker firewall response. Pattern 2: Deny specific paths, allow everything else
security:
  firewall:
    rules:
      - dst: "example.com"
        proto: http
        port: 80
        action: allow
        path_rules:
          - path: "/evil"
            action: deny
        path_default: allow
All paths are forwarded except those starting with /evil, which get a 403. Mixed protocol examples:
security:
  firewall:
    rules:
      - dst: "api.example.com"
        proto: tls
        port: 443
        action: allow
        path_rules:
          - path: "/v1/chat"
            action: allow
        path_default: deny
      - dst: "git.internal.corp"
        proto: ssh
        port: 22
        action: allow
      - dst: "10.0.0.5"
        proto: tcp
        port: 8080
        action: allow
      - dst: "example.com"
        proto: http
        port: 80
        action: allow

Global vs Project Rules

  • System-required rules (from cfg.RequiredFirewallRules()) are always present --- Claude API, OAuth, error tracking, and feature flags
  • Project rules come from add_domains and rules in your .clawker.yaml
  • Rules merge additively --- project rules add to (never replace) system rules
  • Dedup key: destination:protocol:port --- duplicate rules are silently ignored
  • This means you cannot accidentally override or remove a system-required rule

CLI Commands

All firewall operations are available under clawker firewall:
CommandPurpose
clawker firewall statusShow firewall health, running containers, rule count
clawker firewall listList all active egress rules
clawker firewall add DOMAINAdd a domain allow rule
clawker firewall remove DOMAINRemove a domain rule
clawker firewall reloadForce regenerate Envoy/CoreDNS configs
clawker firewall upStart the firewall daemon (usually automatic)
clawker firewall downStop the firewall daemon
clawker firewall enable --agent AGENTAttach a container to the firewall network
clawker firewall disable --agent AGENTRemove a container from the firewall network
clawker firewall bypassTemporary unrestricted egress (eBPF bypass flag + timed re-enable)
clawker firewall rotate-caRegenerate CA and all domain certificates

Certificate Management

The firewall uses a self-signed certificate authority for TLS inspection. All HTTPS traffic is terminated at Envoy with per-domain certificates, inspected at the HTTP level (making request paths, methods, and response codes visible), then re-encrypted upstream.
  • An ECDSA P256 CA is auto-generated during clawker build and baked into agent container images via update-ca-certificates
  • Per-domain certificates are generated for every TLS rule --- Envoy terminates TLS for all allowed domains
  • Domains with path_rules get per-path routing; domains without get allow-all routing --- both go through TLS inspection
  • The CA keypair is persisted in the firewall data directory and shared between the bundler and firewall manager
  • Use clawker firewall rotate-ca to regenerate the CA and all domain certs
After rotating the CA, you must restart any running agent containers for them to pick up the new certificate.

Tools with custom CA bundles

Some tools (notably Python packages installed via uv/uvx, like semgrep) ship their own CA certificate bundles and ignore the system trust store. Clawker sets SSL_CERT_FILE and CURL_CA_BUNDLE in the container environment to point these tools at the system store, which includes the firewall CA. If a tool still reports certificate errors, it may need its own environment variable. Add it to your project config:
# clawker.yaml
agent:
  env:
    REQUESTS_CA_BUNDLE: "${SSL_CERT_FILE}"
SSL_CERT_FILE and CURL_CA_BUNDLE are set automatically in the container. They point at the system CA bundle which includes the firewall CA. Use ${SSL_CERT_FILE} when configuring additional tools rather than hardcoding paths.

Bypass (Escape Hatch)

For situations where you need temporary unrestricted network access:
# Grant unrestricted egress for 5 minutes
clawker firewall bypass 5m --agent dev

# Cancel bypass early
clawker firewall bypass --stop --agent dev
Bypass mode sets the eBPF bypass flag, completely bypassing both DNS filtering and TLS inspection. The flag auto-clears after the specified timeout, re-enabling the firewall.
Bypass mode removes all network restrictions for the specified agent. Use it sparingly and with short timeouts. The agent has full internet access during a bypass.

Disabling the Firewall

To disable the firewall entirely, set firewall.enable to false in your settings.yaml (not the project config):
# ~/.config/clawker/settings.yaml
firewall:
  enable: false
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.

Troubleshooting

Health check failures

Run clawker firewall status to see the health of the stack (Envoy, CoreDNS, and the eBPF subsystem). Envoy is probed over clawker-net on its internal admin port (HTTP GET /), and CoreDNS is probed on its host-published health port 18902 (HTTP GET /health). The eBPF subsystem is considered healthy when the pinned programs and maps under /sys/fs/bpf/clawker/ are present — they survive across firewall stack restarts by design. If a container is unhealthy, try:
clawker firewall down
clawker firewall up

Blocked domains

Check which rules are active with clawker firewall list. If a domain you need is missing, add it:
clawker firewall add api.openai.com
Or add it permanently in .clawker.yaml:
security:
  firewall:
    add_domains:
      - "api.openai.com"

DNS resolution failures

CoreDNS returns NXDOMAIN for any domain not in the allowlist. If an agent reports DNS failures for a domain it should be able to reach, verify the domain is in your rules with clawker firewall list. Docker internal names (host.docker.internal, monitoring stack containers like otel-collector) are forwarded by CoreDNS back to Docker’s embedded DNS and should resolve automatically. If they don’t, check that CoreDNS is running on clawker-net with clawker firewall status.

Certificate trust errors

If an agent reports TLS certificate errors (CERTIFICATE_VERIFY_FAILED, unable to get local issuer certificate):
  1. Check if the tool uses the system trust store. Most tools (Go, curl, wget) do. Python tools installed via uv/uvx may not --- see Tools with custom CA bundles above.
  2. Rotate the CA if the certificate is expired or corrupted:
clawker firewall rotate-ca
Then rebuild the image (clawker build) and restart containers.

Stale dns_cache or route_map after upgrade

On startup the eBPF loader detects pinned maps whose key/value sizes have changed (for example, after a clawker upgrade that ships a new route_key layout) and removes them before reloading. If you still suspect a stale pin, bring the firewall fully down and back up:
clawker firewall down
clawker firewall up
This stops the firewall stack and flushes eBPF state, so the next bringup rebuilds the pinned maps from scratch.

IPv6-only service unreachable

If a service you need is only reachable over native IPv6, the firewall will deny the connection. Most services also publish IPv4 (or dual-stack), which dual-stack clients pick up automatically. There is no opt-in setting for native IPv6 at this time.