Problem
Raw TCP and SSH protocols have no domain-level metadata (no SNI equivalent), so the firewall cannot determine the intended destination at the protocol layer. Currently, iptables creates a single DNAT rule per destination port that captures all outbound traffic on that port and routes it to a dedicated Envoy TCP listener, which forwards to the whitelisted domain. Envoy resolves the domain at connection time.
This means:
- A
proto: ssh rule for github.com on port 22 captures all port 22 traffic — connections to any IP end up at GitHub
- If multiple rules target the same port (e.g., SSH rules for both
github.com and gitlab.com on port 22), iptables first-match wins — the second rule's Envoy listener exists but never receives traffic
- Only one domain can effectively be whitelisted per non-TLS/http port
Why it works this way
Resolving domains to IPs and writing per-IP iptables rules was considered but rejected — IPs change frequently for large services (CDN edge rotation, load balancer failover, anycast shifts), making IP-based rules brittle and a maintenance burden. Port-level DNAT with Envoy handling DNS resolution at connect time was the pragmatic solution.
Desired behavior
Support multiple domains on the same non-TLS/http port (e.g., SSH to both github.com and gitlab.com on port 22). This likely requires a control plane that can manage dynamic IP resolution and iptables rule updates, or an alternative approach to TCP routing that doesn't rely on static port-based DNAT.
Current documentation
The behavior and rationale are documented in:
docs/threat-model.mdx — TCP/SSH port-level routing section
docs/firewall.mdx — protocol behavior warning
docs/security.mdx — TCP/SSH port-level routing section
SECURITY.md — pentester warning
claude-plugin/clawker-support/skills/clawker-support/reference/troubleshooting.md
Notes
Deferring solution design until the control plane work is complete — it may provide the infrastructure needed for dynamic IP-based routing.
Problem
Raw TCP and SSH protocols have no domain-level metadata (no SNI equivalent), so the firewall cannot determine the intended destination at the protocol layer. Currently, iptables creates a single DNAT rule per destination port that captures all outbound traffic on that port and routes it to a dedicated Envoy TCP listener, which forwards to the whitelisted domain. Envoy resolves the domain at connection time.
This means:
proto: sshrule forgithub.comon port 22 captures all port 22 traffic — connections to any IP end up at GitHubgithub.comandgitlab.comon port 22), iptables first-match wins — the second rule's Envoy listener exists but never receives trafficWhy it works this way
Resolving domains to IPs and writing per-IP iptables rules was considered but rejected — IPs change frequently for large services (CDN edge rotation, load balancer failover, anycast shifts), making IP-based rules brittle and a maintenance burden. Port-level DNAT with Envoy handling DNS resolution at connect time was the pragmatic solution.
Desired behavior
Support multiple domains on the same non-TLS/http port (e.g., SSH to both
github.comandgitlab.comon port 22). This likely requires a control plane that can manage dynamic IP resolution and iptables rule updates, or an alternative approach to TCP routing that doesn't rely on static port-based DNAT.Current documentation
The behavior and rationale are documented in:
docs/threat-model.mdx— TCP/SSH port-level routing sectiondocs/firewall.mdx— protocol behavior warningdocs/security.mdx— TCP/SSH port-level routing sectionSECURITY.md— pentester warningclaude-plugin/clawker-support/skills/clawker-support/reference/troubleshooting.mdNotes
Deferring solution design until the control plane work is complete — it may provide the infrastructure needed for dynamic IP-based routing.