[Security Review] Daily Security Review and Threat Modeling — 2026-04-04 #1659
Closed
Replies: 1 comment
-
|
This discussion was automatically closed because it expired on 2026-04-11T13:48:37.093Z.
|
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 a deep, evidence-based security analysis of the
gh-aw-firewallcodebase conducted on 2026-04-04. The overall security posture is strong — the architecture is well-thought-out with multiple defence-in-depth layers. No critical vulnerabilities were found. Two high-severity findings relate to a silent security control bypass (subdomains: falsein YAML rulesets has no effect) and container-escape-capable syscalls remaining in the seccomp allowlist (unshare,setns,mount,open_by_handle_at). Several medium and low findings are documented below.🔍 Phase 1: Context from Previous Security Testing
No complementary firewall-escape-test agent results were available (no such workflow found). This analysis stands on its own codebase review.
🛡️ Architecture Security Analysis
Network Security Assessment
Command run:
Findings (positive):
The iptables architecture implements solid layered controls:
IPv6 disabled at init (
setup-iptables.shlines 47–53):DNS pinning — Only configured DNS servers (Google 8.8.8.8 by default) receive UDP/TCP port 53 from inside the agent. All other UDP is dropped by a blanket
iptables -A OUTPUT -p udp -j DROPrule.Dangerous port blocking — 15 ports are explicitly enumerated in
setup-iptables.shmatchingDANGEROUS_PORTSinsquid-config.ts: SSH(22), Telnet(23), SMTP(25), POP3(110), IMAP(143), SMB(445), MSSQL(1433), Oracle(1521), MySQL(3306), RDP(3389), PG(5432), Redis(6379), MongoDB(27017/27018/28017).DNAT redirection — All port 80/443 TCP is redirected to Squid via DNAT before it can leave the container. Proxy-unaware tools are blocked at the TLS level (Squid rejects raw ClientHellos).
Squid rule ordering is correct — The
accessRulesSectionanddenyRuleinsrc/squid-config.ts(line ~596) are emitted BEFOREhttp_access allow localnet. Squid processes rules in order; a request matching thedeny !allowed_domainsrule is rejected before reaching theallow localnetrule. The comment "This applies to all sources including localnet" confirms this intent.Direct IP connection blocking — Squid config includes:
This prevents bypassing domain filtering by connecting to raw IPs.
Host-level DOCKER-USER chain (
src/host-iptables.ts) — A dedicatedFW_WRAPPERchain is created to filter traffic at the Docker bridge layer, providing a second enforcement point outside the container.Squid has its own
dns_nameserversdirective pointing directly to upstream DNS (not through Docker embedded DNS). This means Squid bypasses the container-level DNS filtering enforced by iptables. While Squid's DNS is used only for the proxy's own resolution (and its egress is filtered by the host-level DOCKER-USER chain), this is an architectural asymmetry worth noting.Container Security Assessment
Command run:
cat containers/agent/seccomp-profile.json grep -n "cap_drop\|cap_add\|security_opt\|no-new-privileges\|pids_limit" src/docker-manager.tsFindings (positive):
no-new-privileges:trueis applied to the agent container (src/docker-manager.ts:1235)src/docker-manager.ts:1236)cap_add: ['SYS_CHROOT', 'SYS_ADMIN'](needed for chroot setup), then both dropped viacapshbefore user code runs (entrypoint.sh:357)cap_add: ['NET_ADMIN', 'NET_RAW'],cap_drop: ['ALL']; agent container never holdsNET_ADMIN6gwith swap disabled when user specifies a limitunshare,setns,mount,open_by_handle_atare allowed in the custom seccomp profileunshare— creates new namespaces (network, mount, etc.). WithCAP_SYS_ADMINdropped after chroot setup, this is limited butCLONE_NEWUSER(user namespace creation) may still be possible without capabilities in some kernel configurations.setns— joins another process's namespace. If an agent process obtains a file descriptor to another namespace (e.g., via/proc), it could join it.mount— needed during chroot setup (mounting procfs), but the seccomp profile doesn't remove it after the capability drop phase. AfterCAP_SYS_ADMINis dropped viacapsh,mountitself will fail. However, if there is any capability leak,mountremains available.open_by_handle_at— the "Shocker" container escape vector. This syscall combined withname_to_handle_at(also ALLOW) and a file descriptor to a host filesystem mount point can allow accessing host files outside the container's view. See [CVE-2014-0038]((nvd.nist.gov/redacted) style attacks. While the selective bind mounts reduce exposure, the syscall being allowed is a risk.The
defaultAction: SCMP_ACT_ERRNO(good — block-by-default) combined withptrace,process_vm_readv,process_vm_writevbeing explicitly blocked is positive. But the above four syscalls create residual container escape surface.The iptables-init container has
NET_ADMINandNET_RAWcapabilities and uses Docker's default (more permissive) seccomp profile rather than the AWF custom one. This is acceptable given the container exits immediately after runningsetup-iptables.sh, but represents unnecessary attack surface during its brief lifetime.Domain Validation Assessment
Command run:
Findings (positive):
validateDomainOrPattern()correctly rejects: empty strings,*,*.*, any pattern matching^[*.]+$, and dangerous characters:[\s\0"'`;#\]`.SQUID_DANGEROUS_CHARScheck is applied again at config generation time viaassertSafeForSquidConfig()— double validation for injection prevention.*is converted to[a-zA-Z0-9.-]*(bounded character class), preventing ReDoS in Squid regex evaluation.(domain/redacted) vs(domain/redacted)`) are supported and correctly generate separate Squid ACLs.subdomains: falsein YAML ruleset files is silently ignored — always enables subdomain matchingA user who writes a ruleset like:
...will find that
api.internal.example.comis also allowed, contrary to the documented intent. Thesubdomainsfield is parsed and stored in theRulestruct butexpandRule()ignores it entirely. This creates a false sense of security for operators who use YAML rulesets expecting exact-match control.The code comment at line 140 acknowledges this: "When subdomains is false, the domain is prefixed with 'exact:' to signal exact-match-only behavior. However, since the current squid config always adds subdomain matching, we return just the bare domain." But operators reading only the YAML API docs will not see this caveat.
Recommendation: Either implement exact-match support, or raise an error/warning when
subdomains: falseis specified.Input Validation Assessment
Command run:
Findings (positive):
execa()with argument arrays, never shell string interpolation — no shell injection risk from CLI inputs.setup-iptables.shuses\$\{}variable expansion iniptablescalls, but values are validated first withis_valid_port_spec()before use.isValidPortSpec()and Bashis_valid_port_spec()functions are consistent — both reject leading zeros and enforce[1-9][0-9]{0,4}for ports.AWF_SQUID_CONFIG_B64environment variable encoding prevents Squid config injection via Docker environment passing.subdomains:false in expandRule()
Capability drop sequence
✅ Recommendations
🔴 Critical
None identified.
🟠 High — Should Fix Soon
H1: Remove
open_by_handle_atfrom seccomp allowlist (or add to explicit blocklist)containers/agent/seccomp-profile.jsonopen_by_handle_atandname_to_handle_atto the SCMP_ACT_ERRNO blocklistH2: Document or fix
subdomains: falsein YAML rulesetssrc/rules.ts:expandRule(),docs/, ruleset YAML schemaexact:prefix support sosubdomains: falsegeneratesacl allowed_domains dstdomain exact-domain.com(not.exact-domain.com)subdomains: falseis specified until it is implemented🟡 Medium — Plan to Address
M1: Add
unshare,setnsto seccomp blocklistcontainers/agent/seccomp-profile.jsonunshare(at minimumCLONE_NEWNETpath via argument filter) andsetns; these are not needed by any standard agent workflowM2: Apply seccomp profile to iptables-init container
src/docker-manager.ts:1336security_opt: ['no-new-privileges:true', 'seccomp=/path/to/iptables-init-seccomp.json']to the iptables-init service; create a minimal seccomp for iptables operationsM3: Squid PID limit for squid-proxy container
src/docker-manager.ts:~453(squid service definition)pids_limit)pids_limit: 50to the squid-proxy serviceM4: Add YAML ruleset file size guard
src/rules.ts:loadRuleSet()yaml.load()(e.g., reject files > 1MB)🟢 Low — Nice to Have
L1: Rate-limit Squid log writes for long-running sessions
read_timeoutis 30 minutes — long-lived SSE connections could generate large access.log entries in busy sessionsL2: Add an explicit note to ruleset YAML schema about URL-path filtering not being supported
--allow-domains github.comallows all paths on github.com; operators may not realize URL-path filtering requires--url-patterns(SSL Bump)📈 Security Metrics
Beta Was this translation helpful? Give feedback.
All reactions