Firewall Runtime UAT
SkillFiles & storageLive 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.
No other account needed.
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 aclawker.yamlegress edit (security.firewall.add_domains/security.firewall.rules) without a restart, have them runclawker firewall refresh(global, no--agent; add/update only — deletes still go throughclawker 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)
| observation | meaning |
|---|---|
NXDOMAIN / "could not resolve host" | host not in allowlist — blocked at CoreDNS (layer 2) |
HTTP 403, body Forbidden\n | clawker host/path deny (firewallBlockedBody) — Envoy path/vhost gate |
HTTP 403, EMPTY body, server: envoy | Envoy 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_disconnect | client 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