[Security Review] Daily Security Review & Threat Model — 2026-05-17 #3296
Closed
Replies: 1 comment
-
|
This discussion was automatically closed because it expired on 2026-05-24T12:38:19.029Z.
|
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 covers the
gh-aw-firewall(AWF) codebase as of 2026-05-17. The system implements a layered defense model: host-levelDOCKER-USERiptables rules, container-level NAT/filter chains, a Squid L7 proxy, capability dropping, chroot isolation, and seccomp confinement. The overall security posture is strong with well-structured, defense-in-depth controls. No critical vulnerabilities were identified. A small number of medium/low-severity design-level observations are documented below.Key metrics:
containers/agent/seccomp-profile.json)containers/agent/setup-iptables.sh)containers/agent/entrypoint.sh)src/squid/policy-manifest.ts:10–32)SQUID_DANGEROUS_CHARSregex (src/domain-patterns.ts:27) andassertSafeForSquidConfig()(src/squid/domain-acl.ts:28)🔍 Findings from Firewall Escape Test
The pre-fetched escape test file (
/tmp/gh-aw/escape-test-summary.txt) contained CI workflow execution metadata rather than test findings — it appears to be logs from a "Secret Digger (Copilot)" run that ended withGH_AW_SECRET_VERIFICATION_RESULT: successandGH_AW_AGENT_CONCLUSION: success. The agent emitted only anoopoutput, indicating no secrets were found/exfiltrated during that run. This is consistent with the credential isolation mechanisms verified in the codebase below.🛡️ Architecture Security Analysis
Network Security Assessment
Evidence collected:
containers/agent/setup-iptables.sh(full review)src/host-iptables-rules.ts(host-level DOCKER-USER chain setup)Findings:
IPv6 egress disabled (
setup-iptables.sh:~50-55):IPv6 is disabled inside the agent container to prevent bypassing the IPv4-only DNAT rules. This is a critical mitigation against Happy Eyeballs / dual-stack bypass.
Dual-layer enforcement (NAT + FILTER): Port 80/443 traffic is DNAT'd to Squid at layer 3, and then Squid enforces domain ACLs at layer 7. Any proxy-unaware tool that ignores
HTTPS_PROXYwill have its TCP stream rejected with a TLS error at Squid — still blocked, just with a different error.DNS restriction:
resolv.confis overwritten to only list127.0.0.11(Docker embedded DNS). Direct UDP/TCP port 53 to non-configured servers is blocked by the OUTPUT filter chain. This prevents DNS-based data exfiltration.Dangerous port blacklist (
src/squid/policy-manifest.ts:10): 24 ports covering SSH, SMTP, databases, Redis, MongoDB, RDP, etc. are blocked both at Squid and at the iptables NAT level.Observation (Medium): The
AWF_ENABLE_HOST_ACCESSflag causessetup-iptables.shto add blanket NAT RETURN rules for bothhost.docker.internaland theNETWORK_GATEWAY_IP. This means any TCP traffic destined for the host gateway fully bypasses Squid for L7 inspection. Port restrictions (--allow-host-ports) apply only to the FILTER chain, so HTTP/HTTPS traffic to the host is permitted but unlogged at the L7 level. This is a deliberate design tradeoff (MCP servers need this), but it means L7 audit coverage has a gap when host access is enabled.Observation (Low):
setup-iptables.shparsesAWF_DNS_SERVERSfrom the environment without validation beyond basic IP format checking. A malformed IPv6 address with shell metacharacters could potentially escape — but since the value originates from the TypeScript CLI which performs its own validation, the risk is low in practice.Container Security Assessment
Evidence collected:
containers/agent/entrypoint.sh(full review)containers/agent/seccomp-profile.json(414 lines)capsh,cap_drop,NO_NEW_PRIVSFindings:
UID/GID validation (
entrypoint.sh:~14-32): Explicit numeric regex check and prohibition ofUID=0/GID=0prevents privilege escalation throughAWF_USER_UID/AWF_USER_GIDenvironment variables.Capability drop (
entrypoint.sh:~927):capsh --drop=<caps>drops capabilities from the bounding set before running user code.SYS_CHROOTandSYS_ADMINare explicitly mentioned in documentation as dropped. This is an irreversible drop from the bounding set.Seccomp profile:
containers/agent/seccomp-profile.jsonat 414 lines implements a syscall allowlist, which is a strong defense against container breakout via kernel exploitation.chroot to
/host: User code runs chrooted, limiting filesystem access to explicitly mounted paths./etc/shadowis excluded. Home directory mounts are limited to a whitelist (.cache,.config,.local,.anthropic,.claude, etc.).procfs with
hidepid=2: The container-scoped procfs mounted at/host/procuseshidepid=2, preventing the agent from reading/proc/[pid]/environof other processes.Observation (Medium): The entrypoint script runs
nodeinline to update Claude config files (entrypoint.sh:~190-200), e.g.:node -e "const fs = require('fs'); const f = process.env.AWF_CONFIG_FILE; ..."The
AWF_CONFIG_FILEpath is derived from$HOMEwhich is an environment variable. If an attacker could influenceHOMEorAWF_CONFIG_FILEbefore entrypoint runs, they might redirect the write. However, this runs as root inside the container before the privilege drop, limiting the practical impact. The value is constructed from known paths, not from untrusted input.Observation (Low):
capshavailability is checked on the host viachroot /host which capsh. If the host does not havecapshinstalled, the entrypoint exits with an error (entrypoint.sh:~591-594) — this is the correct fail-safe behavior, but it means AWF is dependent on host tooling for its privilege-drop guarantee.Domain Validation Assessment
Evidence collected:
src/domain-patterns.ts(full review)src/squid/domain-acl.ts(assertSafeForSquidConfig)Findings:
Squid injection prevention:
SQUID_DANGEROUS_CHARS = /[\s\0"';#]/is checked at parse time (validateDomainOrPattern), andassertSafeForSquidConfig()indomain-acl.ts` provides a defense-in-depth check at interpolation time. Backslash is explicitly rejected for domain names.Wildcard safety: Wildcard patterns (
*.foo.com) are converted to regex usingwildcardToRegex()with character-class restriction[a-zA-Z0-9.-]*to prevent ReDoS and regex injection.ReDoS protection (
domain-patterns.ts:~94): Comment explicitly notes the use of[a-zA-Z0-9.-]*instead of.*to prevent catastrophic backtracking.Observation (Low): The
--allow-urlsfeature uses a different validation path (SQUID_DANGEROUS_CHARSonly, without the backslash restriction) because URL regex patterns legitimately use\. This is correct but worth noting as a potential future audit point if URL pattern complexity increases.Input Validation Assessment
Evidence collected:
src/commands/validate-options.tssrc/host-iptables-rules.ts(isValidPortSpec)Findings:
Port specification validation:
isValidPortSpec()inhost-iptables-rules.tsrejects leading zeros and enforces 1–65535 range. The same logic is mirrored insetup-iptables.sh'sis_valid_port_spec(), creating dual-layer validation.No
evalornew Functionin production code: grep ofsrc/(excluding tests) found no dynamic code execution patterns.execaused for subprocess execution: The codebase usesexeca(notchild_process.execwith shell=true) for docker-compose invocations, preventing shell injection via command arguments.CONNECT), no SSL inspectionformatDomainForSquidadds leading dot;assertSafeForSquidConfigrejects special chars/etc/resolv.confpost-chroot to redirect DNSresolv.confis written before chroot; chroot limits write accessNET_ADMIN; only init container doesTCP_DENIED/TCP_TUNNELwith timestamps; iptablesLOGrules capture non-HTTP/proc/[pid]/environto steal API keyshidepid=2on procfs mount prevents this/etc/shadowor unwhitelisted$HOMEpathsresolv.confrestricted to127.0.0.11; OUTPUT chain drops UDP/TCP :53 to non-configured servers--limit 5/minto protect kernel bufferSYS_CHROOT/SYS_ADMINcapabilitiescapsh --dropremoves from bounding set before user code; irreversibleAWF_USER_UID=0sysctl net.ipv6.conf.all.disable_ipv6=1inside container🎯 Attack Surface Map
src/domain-patterns.ts:validateDomainOrPatternSQUID_DANGEROUS_CHARS+ backslash rejection +assertSafeForSquidConfigat interpolationsrc/host-iptables-rules.ts:isValidPortSpecTCP_DENIEDon non-whitelistedHTTPS_PROXYenv + DNAT fallback → Squid:3128resolv.conf+ OUTPUT chain127.0.0.11in resolv.conf; UDP/TCP :53 drop for non-configured serversAWF_ENABLE_HOST_ACCESSflag/host//etc/shadowexcluded; home uses allowlist/host/prochidepid=2prevents cross-process environ readscapsh --dropfrom bounding set; seccomp profile (414-line allowlist)AWF_USER_UID,AWF_USER_GID,AWF_CONFIG_FILEetc.✅ Recommendations
🔴 Critical
None identified.
🟠 High
None identified.
🟡 Medium
Document the L7 audit gap for host-access mode: When
--enable-host-accessis used, HTTP/HTTPS traffic to the host gateway bypasses Squid and is not logged at L7. Consider adding a warning in the CLI output and in documentation when this flag is enabled, so users understand the audit coverage reduction.Consider rate-limiting Squid connections: The current Squid config does not include connection rate limits (
max_connections,delay_pools). A runaway or adversarial agent could flood Squid or exhaust host TCP connections. Addingmax_connections 500or adelay_poolfor the agent IP would mitigate DoS scenarios.API proxy sidecar access is implicit: When
--enable-api-proxyis active, any process in the agent container can call `(172.30.0.30/redacted) and receive an authenticated response. There is no per-process or per-credential token binding. This is a deliberate design choice (the agent is the trusted party), but if an agent is compromised and can spawn subprocesses, those subprocesses inherit API access. Consider documenting this trust boundary explicitly.🟢 Low
capshhost dependency: The privilege-drop guarantee depends oncapshbeing present on the host (/host/usr/sbin/capshor equivalent). Consider either bundlingcapshin the agent container image (avoiding the host dependency) or providing a more informative error message that includes remediation steps.Shell variable injection in
setup-iptables.sh:AWF_DNS_SERVERSis split withIFS=','and iterated. Each element is trimmed withtr -d ' 'but not validated against an allowlist before being used iniptables -d "$dns_server". While TypeScript validates this before passing it to Docker, adding IP format validation in the shell script as well would make the script safe to run standalone.--allow-urlsbackslash exception: URL regex patterns allow\(backslash) throughSQUID_DANGEROUS_CHARSvalidation (intentionally). Ensure this exception is documented in the security model and that the Squid regex engine's behavior with attacker-influenced backslash sequences is periodically audited.📋 Evidence Collection
Key files reviewed
containers/agent/setup-iptables.sh— ~420 lines, full reviewcontainers/agent/entrypoint.sh— full review (UID validation, DNS config, SSL Bump, iptables wait, capsh drop)containers/agent/seccomp-profile.json— 414 linessrc/domain-patterns.ts—SQUID_DANGEROUS_CHARS,validateDomainOrPattern,wildcardToRegexsrc/squid/domain-acl.ts—assertSafeForSquidConfig,formatDomainForSquidsrc/squid/policy-manifest.ts—DANGEROUS_PORTS(24 ports listed)src/host-iptables-rules.ts—isValidPortSpec,setupHostIptables, DOCKER-USER chain setupsrc/domain-patterns.ts— ReDoS protection comment, wildcard character class restrictionCommands run
📈 Security Metrics
setup-iptables.sh,entrypoint.sh)Beta Was this translation helpful? Give feedback.
All reactions