Firewall Runtime UAT

SkillFiles & storage

Live egress probes that prove firewall enforcement from inside a clawker container. Use after any change to controlplane/firewall generator or rule code, before you declare that work complete, and for an authorized firewall UAT task. Golden files and envoy validate do not cover this.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Firewall Runtime UAT skill

What this skill tells your AI

The instructions your AI receives, as published by schmitthub/clawker in .agents/skills/firewall-uat/SKILL.md and read by ahel’s review.

Golden files + envoy --mode validate prove the generated config is valid. They do NOT prove behavior. Behavior is verified live, and you are the vehicle: when $CLAWKER_AGENT is set, this agent session runs inside a clawker agent container whose egress is routed through the live firewall stack (eBPF redirect → Envoy → CoreDNS). Exercising egress from this shell IS the behavioral test.

Roles

  • You (in-container): exercise egress with the probe tools below. You CANNOT run host clawker; the CLI runs on the host only.
  • User (host operator): mutates rules host-side — clawker firewall add <host> [--proto https|http|ssh|tcp|wss|...] [--port N|lo-hi] [--path /p --action allow|deny]. Ask them to add/remove rules; then you re-probe. To live-apply a clawker.yaml egress edit (security.firewall.add_domains / security.firewall.rules) without a restart, have them run clawker firewall refresh (global, no --agent; add/update only — deletes still go through clawker firewall remove).

clawker firewall add flags: --proto (default https), --port (default proto-specific; single port 443 or inclusive range 9000-9100), --path + --action (path-scoped rule, required together).

Probe tools available in-container

curl, ssh (+ forwarded host agent), nghttp + h2load (HTTP/2 + h2 WS; h2load also does h3 — useful for the QUIC/alt-svc sibling), websocat, wscat, python3, openssl, gh, git. nc is absent. The SSH agent is the host's, live-mirrored through the socketbridge (fresh net.Dial per connection — no cache; ssh-add -l reflects host agent state at that instant).

No tool here drives an h2/h3 Extended CONNECT WebSocket (websocat = h1.1 only; nghttp -u = h2c upgrade, not WS). So WS-over-h1.1 is runtime-testable (websocat wss://…/ws/echo → C2 echoes the frame back through the MITM), but the h2 (allow_connect) / h3 (allow_extended_connect) WS paths are only config-confirmable (grep -c allow_connect), not live-drivable in-container.

If the harness runs shell commands in a network sandbox, the probe cannot reach the firewall. Disable the sandbox for probe commands (Claude Code: dangerouslyDisableSandbox: true on the Bash tool).

Behavioral discriminators (HARD-won — memorize)

observationmeaning
NXDOMAIN / "could not resolve host"host not in allowlist — blocked at CoreDNS (layer 2)
HTTP 403, body Forbidden\nclawker host/path deny (firewallBlockedBody) — Envoy path/vhost gate
HTTP 403, EMPTY body, server: envoyEnvoy upgrade refused (WS upgrade on a non-wss route — no upgrade_configs) — NOT a clawker deny
server: envoy + x-envoy-upstream-service-time + upstream's own code (e.g. 404)request reached the real upstream = allowed
ssh -Tv → Authenticated to <host> ([<EnvoyIP>]:<port>)shows the Envoy hop + the dedicated-listener port (TCPPortBase+idx, one per opaque host)
access log response_code:0 + response_flags:DC + response_code_details:downstream_remote_disconnectclient disconnected before the response — NOT a block (action:allowed). Normal uv/pip/npm h2 stream-cancel churn.

clawker-net is NOT an egress-test surface. Intra-net traffic (a sibling container's clawker-net IP — e.g. the C2's) is INTENTIONALLY open, not redirected to Envoy; hitting it bypasses the firewall by design and false-positives a leak / confused-deputy. An egress-attack test needs a PUBLIC dst (through Envoy), e.g. the C2's ngrok edge — never its clawker-net IP. Confused-deputy's structural defense is config-verifiable instead: FQDN flows are all LOGICAL_DNS/DFP, ZERO ORIGINAL_DST/use_original_dst (those appear only for IP/CIDR rules, the range-validated carve-out).

SSH routing is special — banner is the only proof. Any real SSH server completes a valid handshake, so a misroute ("everything funneled to host X") still returns a valid ssh -T response. The TRUE upstream is discriminated by the banner / remote software version (GitHub: …version 6279353 + "does not provide shell access"; GitLab: GitLab-SSHD + "Welcome to GitLab"). For opaque-host fan-out (n≥2), also confirm each host lands on a distinct [<EnvoyIP>]:<port> — same port = collision/misroute.

Permission denied (publickey) on an opaque-ssh probe is a routing PASS (real daemon negotiated auth then rejected the key) — auth ≠ routing.

Spot-checking the live generated config

Dev config (when .clawkerlocal/ XDG overrides are active): .clawkerlocal/.local/share/clawker/firewall/envoy.yaml — ~31k lines, grep/ sed only, never full-read. Corefile / rules / certs are siblings. Use it to ground a runtime result in the artifact (e.g. grep -c upgrade_configs to confirm 0 when no wss rule exists). The .clawkerlocal/ override layout is in the Serena project-guide memory.

Adversarial C2 harness

test/adversarial/ — Go C2 + SQLite, exposed via ngrok at ajschmitt.ngrok.app (stable host). For exfil-block tests: try to reach a non-allowlisted host/path/ proto and confirm the block. Allowlisted subset is operator-controlled per run (e.g. https /ws/ + /allowed/ only). See test/adversarial/AGENTS.md.

What golden/validate cannot catch (only live UAT can)

CP↔generator contract gaps. A past regression dropped the Envoy health listener: golden files and validate passed, and CP bringup hung on the readiness probe. Run live UAT before declaring the generator branch done.

Signals

GitHub stars
55
Forks
6
Last commit
Sep 2026
Advanced
Item type
skill
Key
firewall-uat
Source
github.com/schmitthub/clawker