[Security Review] Daily Security Review and Threat Modeling — 2026-03-08 #1177
Closed
Replies: 1 comment
-
|
This discussion was automatically closed because it expired on 2026-03-15T13:42:14.469Z.
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
📊 Executive Summary
This review analyzed 5,553 lines of security-critical code across 7 core files, covering the full defense-in-depth stack: host-level iptables, container-level iptables, Squid proxy ACL generation, domain pattern validation, container hardening, and credential protection. No firewall-escape-test workflow exists; analysis is based entirely on static code review with full command evidence.
Overall security posture: GOOD — multiple independent defense layers are correctly implemented. Findings are primarily gaps in the inner-most (container-level) layer that are already compensated by the outer layer, plus one conditional IPv6 bypass that depends on the host Docker configuration.
🔍 Phase 1: Previous Escape Test Context
No
firewall-escape-testworkflow was found in the repository. The closest equivalents aresecret-digger-claude,secret-digger-codex, andsecret-digger-copilot(hourly scheduled), andsecurity-guard(PR gating). This review stands alone as primary threat intelligence.🛡️ Phase 2: Architecture Security Analysis
Network Security — Two-Layer Firewall
The system correctly implements defense-in-depth with two independent enforcement points:
Layer 1 — Container-level (setup-iptables.sh)
Observation: The final DROP rule only blocks TCP. Non-DNS UDP and ICMPv4 outbound are not blocked at the container level.
Layer 2 — Host-level (src/host-iptables.ts)
The host chain correctly blocks UDP and has a default-deny final rule. This fully compensates for Layer 1's gap — but only for IPv4.
Container Hardening
Correctly implemented:
CAP_NET_ADMINis dropped before user code executes, preventing iptables bypass.Domain Validation
Correctly implemented: The anti-ReDoS wildcard replacement is good. Pattern anchoring prevents partial matches.
Credential Protection (One-Shot Token)
Correctly implemented with acknowledged limitations (XOR is not cryptographic; by-design for binary string hiding, not crypto protection).
host-iptables.ts:363— FW_WRAPPER_V6 only created ifipv6DnsServers.length > 0setup-iptables.sh:295— onlyiptables -A OUTPUT -p tcp -j DROPseccomp-profile.json—defaultAction: SCMP_ACT_ALLOW, only 3 deny groupssquid-config.ts:119—url_regex \$\{pattern}without newline sanitization/devread-onlydocker-manager.ts:541—'/dev:/host/dev:ro'setup-iptables.sh— noLOGrules in OUTPUT filter chainsquid-config.ts:458—for (let p = start; p <= end; p++)git config safe.directory '*'disables ownership checkentrypoint.sh:269— global safe.directory wildcard🎯 Attack Surface Map
--allow-domainsCLI flagcli.ts:669validateDomainOrPattern(), anti-ReDoS regex--allow-urlsCLI flagcli.ts:996--allow-host-portsCLI flagcli.ts:778--dns-serversCLI flagcli.ts:762--envCLI flagcli.ts:950--mountCLI flagcli.ts:964AWF_USER_UIDenv varentrypoint.sh:9squid-config.ts:119one-shot-token.cdocker-manager.ts:541📋 Evidence Collection
Finding T1: IPv6 bypass — FW_WRAPPER_V6 only installed when IPv6 DNS servers configured
Analysis: With default config (
--dns-servers 8.8.8.8,8.8.4.4— all IPv4),ipv6DnsServers.length === 0, soFW_WRAPPER_V6is never installed in ip6tables DOCKER-USER. If Docker has IPv6 enabled globally, containers onawf-netmay have IPv6 addresses and could initiate unfiltered outbound IPv6 connections.Partial mitigation:
awf-netis created without--ipv6, so IPv6 routing through that bridge is typically unavailable. The risk is low in most environments but non-zero on Docker installations withip6tables: truein daemon.json.Finding T2: Container UDP/ICMP not blocked at container layer
Analysis: No rule blocks
-p udp(non-DNS) or-p icmpoutbound. The container's OUTPUT filter chain default policy is ACCEPT, so non-DNS UDP and ICMP packets exit the container unimpeded at the iptables level.Full mitigation exists at host level:
host-iptables.tsFW_WRAPPER chain at the DOCKER-USER hook drops all UDP (except trusted DNS) and has default-deny. The container-level gap is compensated, but the defense relies on a single (outer) enforcement point for these protocols.Finding T3: Seccomp profile is default-ALLOW
Analysis: The custom profile blocks ptrace (memory inspection), kernel module ops, and umount. Docker's built-in default seccomp profile blocks ~47+ syscalls including
unshare,clonewith new namespaces,name_to_handle_at, etc. The custom profile is notably missing blocks for:unshare— create new namespace (combined with lack ofCLONE_NEWUSERblock)name_to_handle_at/open_by_handle_at— filesystem handle bypasssetns— join another namespaceMitigation:
CAP_NET_ADMINandCAP_SYS_ADMINare dropped viacapshbefore user code runs. These drops make most namespace escape attempts fail regardless of seccomp policy.Finding T4: URL pattern newline injection into Squid config
Analysis: If a URL pattern contained a literal newline (e.g., from reading
--urls-file), the pattern would be split across Squid config lines. The second line could be an arbitrary Squid directive.Example: A file containing
(example.com/redacted) allow allwould injecthttp_access allow allinto the Squid config, bypassing domain restrictions.Existing mitigations: (1)
--allow-urlsrequires--ssl-bump, which is an advanced/unusual mode. (2) TheparseDomains()function splits on commas, but--urls-filereads raw lines. (3) A HTTPS-scheme check runs but does not scan for embedded newlines.✅ Recommendations
🔴 High — Address Soon
H1: Always install FW_WRAPPER_V6 chain when ip6tables is available, regardless of DNS config
Currently, the IPv6 DOCKER-USER chain is only set up when IPv6 DNS servers are explicitly configured. This means IPv6 filtering is absent in the default configuration.
🟠 Medium — Plan to Address
M1: Add UDP and ICMP DROP rules in container-level OUTPUT chain
The container's
setup-iptables.shshould block all non-DNS UDP and ICMP outbound to provide defense-in-depth independent of the host firewall.M2: Harden seccomp profile to block additional dangerous syscalls
The custom seccomp profile should be extended to match or exceed Docker's built-in default seccomp coverage. At minimum, add blocks for:
{ "names": ["unshare", "setns", "name_to_handle_at", "open_by_handle_at", "clone3", "move_mount", "fsopen", "fsmount", "fsconfig", "fspick", "open_tree"], "action": "SCMP_ACT_ERRNO" }🟡 Low — Nice to Have
L1: Sanitize newlines in URL patterns before Squid config generation
L2: Consider whether host
/devneeds to be mounted in chroot modeReview whether all device nodes in
/devare necessary. If only/dev/null,/dev/zero,/dev/random,/dev/urandom, and/dev/ptsare needed, prefer mounting a minimal tmpfs devtmpfs instead of bind-mounting the full host/dev.L3: Add LOG rules for UDP/ICMP drops in container iptables
For forensic completeness, add LOG rules before the final DROP rules in the container's OUTPUT chain (mirroring the host-level
[FW_BLOCKED_UDP]and[FW_BLOCKED_OTHER]prefix logging).ℹ️ Informational
I1: Port range validation loop is O(range_size) —
squid-config.ts:458iterates every port in a user-specified range to check against DANGEROUS_PORTS. For range1000-60000, this is ~59K iterations. Consider using a range intersection check instead.I2: XOR obfuscation in one-shot-token is not cryptographic — Acknowledged in the code comments. No action needed; the mechanism correctly clears env vars from
/proc/self/environafter first read.I3:
git config safe.directory '*'— Set globally for awfuser inentrypoint.sh. This is intentional for workspace compatibility but disables git ownership verification. Document this explicitly in security posture documentation.📈 Security Metrics
Strengths worth calling out:
[a-zA-Z0-9.-]*instead of.*in wildcardsBeta Was this translation helpful? Give feedback.
All reactions