[Security Review] Daily Security Review and Threat Modeling — 2026-03-14 #1299
Closed
Replies: 1 comment
-
|
This discussion was automatically closed because it expired on 2026-03-21T13:44:39.576Z.
|
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
Overall security posture is good. The defense-in-depth architecture (host iptables → container NAT → Squid L7) is well-designed, and critical paths (credential isolation, capability dropping, domain validation) are robustly implemented. No critical vulnerabilities were found.
Two findings warrant attention: an IPv6 proxy bypass (when ip6tables is available in the agent container but no IPv6 DNAT rules exist for ports 80/443) and a dangerous ports list inconsistency between the two enforcement layers. All other findings are low or informational.
🔍 Findings from Firewall Escape Test
The
firewall-escape-testworkflow name was not found in the current workflow registry. Thesecurity-reviewworkflow logs could not be fetched (git not in PATH in this runner context). No complementary escape test report was available for cross-referencing this cycle.🛡️ Architecture Security Analysis
Network Security Assessment
The dual-layer enforcement model is well-designed:
Host level (
src/host-iptables.ts): Creates aFW_WRAPPERchain inDOCKER-USERthat applies to ALL containers on theawf-netbridge. Default-deny for TCP/UDP with explicit allows for Squid egress, DNS forwarding, and conntrack-established return traffic.Container level (
containers/agent/setup-iptables.sh): NAT DNAT rules redirect port 80/443 to Squid (\$\{SQUID_IP}:3128). OUTPUT filter chain enforces default-deny for TCP/UDP independently of NAT.Squid level (
src/squid-config.ts): L7 domain ACL filtering with dstdomain and dstdom_regex ACLs. Explicithttp_access deny allat end of chain.Firewall rule ordering is correct: deny rules for unsafe ports precede allow rules for safe ports in Squid. The NAT DANGEROUS_PORTS RETURN rules precede DNAT redirect rules in the container.
DNS exfiltration prevention: Direct UDP/TCP port 53 traffic to non-configured DNS servers is blocked at both host and container filter chain levels. Only explicitly configured upstream servers (default: Google 8.8.8.8/8.8.4.4) are permitted.
Cloud metadata endpoint:
169.254.0.0/16is explicitly REJECTed in the hostFW_WRAPPERchain (line 385 ofsrc/host-iptables.ts). Port 80 traffic to 169.254.169.254 would also be DNAT'd to Squid and blocked there.Severity: Medium
Evidence:
When
ip6tablesis available inside the agent container, IPv6 HTTP/HTTPS connections (ports 80/443) are not redirected to Squid. TheIP6TABLES_AVAILABLE=truepath only adds loopback (::1) and DNS exemptions to the ip6tables NAT OUTPUT chain — no DNAT redirect for ports 80/443. The ip6tables filter OUTPUT chain is also never populated, so there is no IPv6 DROP rule for TCP/UDP.Additionally, at the host level (
src/host-iptables.ts), theFW_WRAPPER_V6chain is only created when IPv6 DNS servers are configured (hasIpv6Dns = upstreamDns.some(s => s.includes(':'))). With default IPv4-only DNS (8.8.8.8/8.8.4.4), no IPv6 filter chain is inserted intoDOCKER-USERat all.Impact: If a destination server has AAAA records and the Docker network has IPv6 routing, an agent process could make direct IPv6 connections bypassing domain whitelisting entirely.
Mitigating factor: The Docker network (
awf-net) is created without IPv6 enabled (no--ipv6flag insrc/host-iptables.ts:ensureFirewallNetwork()), so containers likely have no routable IPv6 addresses by default. Risk is only realized if Docker's default bridge or IPv6 is otherwise reachable.Recommendation: Add ip6tables DNAT rules for ports 80/443 → Squid, and a default DROP for TCP/UDP in the agent container's setup, mirroring the IPv4 rules. Also unconditionally insert a
FW_WRAPPER_V6default-deny chain intoDOCKER-USERregardless of DNS server type.Container Security Assessment
Capability Management:
NET_ADMINis never granted to the agent container (iptables setup is handled by a separate init container). This is a strong design choice that prevents firewall rule modification by untrusted code.In chroot mode,
CAP_SYS_CHROOTandCAP_SYS_ADMINare dropped viacapshbefore executing user commands (confirmed atcontainers/agent/entrypoint.shlines 303–312).Seccomp Profile: Default action is
SCMP_ACT_ERRNO(secure). 343 syscalls explicitly allowed. Notably:mount,unshare,setns, andchrootare allowed by seccomp but their dangerous use requiresCAP_SYS_ADMIN/CAP_SYS_CHROOTwhich are dropped.unsharewithCLONE_NEWNETto create new network namespaces could theoretically bypass iptables rules (new namespace has no rules), but requiresCAP_SYS_ADMIN(dropped in chroot mode) orCAP_NET_ADMIN(never granted).Non-root execution: Agent runs as
awfuser(UID dynamically matched to host user). Root escalation prevented by UID=0 check in entrypoint (lines 27–34).One-Shot Token: Credential isolation via LD_PRELOAD is well-designed. On first
getenv()call for token variables, the value is cached and removed from/proc/self/environviaunsetenv(). Token names are XOR-obfuscated to preventstringsextraction.Severity: Low
This globally disables git's safe directory ownership check for
awfuser, allowing git operations in any directory regardless of ownership. While necessary for workflows that operate on repositories owned by different UIDs, it removes a protection against git hook attacks in untrusted repositories.Recommendation: Document this intentional trade-off. Consider scoping to specific workspace paths if the use case allows.
Domain Validation Assessment
Pattern validation in
src/domain-patterns.tsis strong:*,*.*, patterns with only wildcards/dots) are rejected[a-zA-Z0-9.-]*character class instead of.*to prevent ReDoS(redacted)https://`) are correctly stripped before validationThis correctly prevents bypassing domain filtering via raw IP addresses in CONNECT requests.
FINDING: Dangerous Ports List Inconsistency Between Layers
Severity: Low
The Squid config's
DANGEROUS_PORTSlist includes 6 ports not in the container iptablesDANGEROUS_PORTSlist. For those 6 ports, the iptables NAT lacks a RETURN rule, so traffic gets DNAT'd to Squid and blocked there. This is functionally equivalent but violates the defense-in-depth principle that each layer should independently enforce the same policy.Recommendation: Synchronize the dangerous ports list between
src/squid-config.tsandcontainers/agent/setup-iptables.sh.Input Validation Assessment
Command injection: User-supplied commands are passed to Docker Compose as an array (
['/bin/bash', '-c', config.agentCommand.replace(/\$/g, '$$$$')]), not via shell. Theexecalibrary is used throughout with array arguments — no shell string interpolation for docker commands.The custom ESLint rule
local/no-unsafe-execaenforces safe execa usage (prevents template literals and string concatenation in command/args positions).Port validation: User-supplied ports via
--allow-host-portsare validated againstDANGEROUS_PORTSand sanitized with/[^0-9-]/gremoval before insertion into Squid config and iptables rules.setup-iptables.sh: no ip6tables DNAT for 80/443validateDomainOrPattern()rejects*,*.*, multi-wildcard patternsunshareallowed in seccomp; mitigated by no CAP_SYS_ADMIN/CAP_NET_ADMIN--enable-dockerused; intentional featureAWF_SQUID_CONFIG_B64; generated, not user-controlled<<merge only in generated YAML, not parsed from user input🎯 Attack Surface Map
--allow-domainsCLI argumentsrc/cli.ts:1243validateDomainOrPattern(), wildcard sanitization--allow-host-portsCLI argumentsrc/squid-config.ts:468/[^0-9-]/gsanitizationsrc/cli.ts:1232awfuser; capability-dropped; iptables enforcedsrc/docker-manager.ts:743--enable-dockerexplicitly set; socket hidden by default/hostmountsrc/docker-manager.ts:577:ro; credential files hidden via/dev/nullbindsrc/docker-manager.ts:314src/squid-config.tscontainers/agent/setup-iptables.sh📋 Evidence Collection
IPv6 bypass investigation commands
Dangerous ports inconsistency analysis
✅ Recommendations
Medium
setup-iptables.ship6tables -t nat -A OUTPUT -p tcp --dport 80 -j DNATand--dport 443 -j DNATrules targeting Squid (if Squid is reachable via IPv6, or block IPv6 outbound entirely)ip6tables -A OUTPUT -p tcp -j DROPand-p udp -j DROPmirror rulessrc/host-iptables.ts, unconditionally createFW_WRAPPER_V6with a default-deny policy and insert it intoDOCKER-USER, regardless of whether IPv6 DNS is configuredLow
Synchronize dangerous ports lists
5984, 6984, 8086, 8088, 9200, 9300toDANGEROUS_PORTSincontainers/agent/setup-iptables.shto matchsrc/squid-config.tsDocument
git safe.directory '*'trade-offcontainers/agent/entrypoint.shexplaining why the wildcard is needed and what protections exist despite itgit config --systemscoped to specific workspace directories if technically feasibleUpdate js-yaml dependency
npm audit fixto resolve the moderatejs-yamlprototype pollution (GHSA-mh29-5h37-fv8m)Informational
Consider adding IPv6 to Docker network explicitly
awf-netwith proper ip6tables rules, or explicitly disable IPv6 at the Docker daemon level for this network to make the current behavior deterministicseccomp: consider restricting
unshareunshare(CLONE_NEWUSER)can be used without privileges on some kernels to create user namespaces withuid_maptricks. Consider adding a conditionalargsfilter onunshareto only allow non-network-namespace flags.📈 Security Metrics
Beta Was this translation helpful? Give feedback.
All reactions