[Security Review] Daily Security Review: Threat Model & Architecture Analysis (2026-08-20) #7560
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-08-27T07:14:16.639Z.
|
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
Security posture of
gh-aw-firewallremains strong: the defense-in-depth architecture (host DOCKER-USER iptables chain + container NAT/filter chain + Squid L7 ACL + capability drops + seccomp/AppArmor +no-new-privileges) has no critical gaps identified in this pass.npm auditshows 0 vulnerabilities across 652 dependencies (31 prod). No test files, TODO/FIXME markers, or obvious injection vectors were found in the newly-reviewed control paths. One prior automated red-team run (Secret Digger) was correctly refused by the agent as a prompt-injection attempt — a positive signal for defense against untrusted-content-driven exfiltration.🔍 Findings from Firewall Escape Test
The pre-fetched escape-test log (
/tmp/gh-aw/escape-test-summary.txt) is from workflow run29286879560("Secret Digger (Copilot)"), dated 2026-07-13. Key facts:noop: "Refused prompt injection attack... This is prohibited by the security policy. No investigation was performed."GH_AW_DETECTION_REASON: threat_detected— the platform's own threat-detection layer independently flagged the run (warningconclusion) and opened tracking issue#6205, showing detection worked in addition to agent-level refusal.🛡️ Architecture Security Analysis
Network Security —
src/host-iptables-rules.ts(340 lines) +containers/agent/setup-iptables.sh(536 lines):DOCKER-USERchain (host-iptables-rules.ts:309-341) is the outer perimeter: allows only Squid IP, established/related, loopback, DNS to whitelisted resolvers, sidecar IPs (API/CLI/DoH proxy), optional host-gateway ports, then explicitly blocks multicast/link-local (:247-289) and does default-deny REJECT with[FW_BLOCKED_OTHER]/[FW_BLOCKED_UDP]audit logging.setup-iptables.sh): DNAT port 80/443 → Squid (configure_http_dnat:743-766), NATRETURNblacklist for 15 dangerous ports (SSH/DB/Redis/Mongo/etc.,DANGEROUS_PORTSarray448-464), finalOUTPUT -p tcp -j DROP/-p udp -j DROPdefault-deny (configure_filter_chain:768-825) with rate-limited audit logging (--limit 10/min) to avoid log-flood DoS.disable_ipv6():473-488) specifically to prevent IPv6 egress bypassing the IPv4-only DNAT/iptables model — a well-reasoned, documented control (references issue Squid proxy rejects IPv6 localhost connections from chroot (transaction-end-before-headers) #1543).parseValidPortSpecs()validates first, then the shellis_valid_port_spec()(:378-389) re-validates fail-closed before any value reaches aniptablesinvocation — good protection against a compromised/mismatched CLI passing malformed strings into shell-executed iptables commands.configure_host_access_rules()(:659-726) trustsgetent hosts host.docker.internalandroute -noutput for gateway IP resolution before validating withis_valid_ipv4; validation is present, but the ACCEPT rules for these gateways are IP-based only when--allow-host-access/--allow-host-portsis explicitly enabled by the operator — this is opt-in, not default-on, so risk is bounded to intentional configurations (e.g., Playwright/MCP local testing).Container Security —
src/services/agent-service.ts:37-108:cap_add: ['SYS_CHROOT','SYS_ADMIN']is granted at the Docker level but is dropped inside the container viacapsh --drop=cap_sys_chroot,cap_sys_admin(entrypoint.sh:430-440,1596-1600,1635) before user code executes — verified in bothrun_chroot_command()andrun_non_chroot_command()paths.cap_drop: ['NET_RAW','SYS_PTRACE','SYS_MODULE','SYS_RAWIO','MKNOD']plusNET_ADMINnever added at all — iptables manipulation is isolated to a separateawf-iptables-initcontainer (cap_add: ['NET_ADMIN','NET_RAW'],cap_drop: ['ALL'], agent-service.ts:345-348) that shares the netns vianetwork_mode: service:agent, so the agent process itself never holds netfilter-manipulation capability at any point in its lifecycle — this is a strong design choice.security_opt: ['no-new-privileges:true', seccomp=<profile>, 'apparmor:unconfined'](:91-95). AppArmor unconfined is a deliberate trade-off to permit themountsyscall for the/host/procbind (needed for .NET/.NET runtimes'/proc/self/exe), justified in comments becauseSYS_ADMINis dropped before user code runs, so the mount capability window is closed before untrusted code executes.tmpfsoverlays hidedocker-compose.yml(which contains plaintext env secrets) and MCP logs from the agent's view (:39-69) — sound mitigation againstcat /tmp/awf-*/docker-compose.ymlsecret exfiltration from within the sandbox.mem_limit,pids_limit,cpu_shares) are present, mitigating DoS/fork-bomb risk.Domain Validation —
src/squid/domain-acl.ts:902-923,src/domain-validation.ts:23:SQUID_DANGEROUS_CHARS = /[\s\0"';#]/blocks whitespace, null byte, quotes, semicolons, backticks, and#from being interpolated intosquid.conf, preventing directive/comment injection via--allow-domains.assertSafeForSquidConfig()` throws (fail-closed) rather than sanitizing-and-continuing, which is the correct posture for config-injection prevention.parseDomainConfig()(:951-979) correctly removes redundant subdomains only when protocol scope is compatible (bothor matching protocol) — avoids silently widening an HTTP-only allow rule to HTTPS or vice versa.Input Validation — UID/GID handling in
entrypoint.sh:36-56:0prevents a caller from re-mapping the sandbox user to root viaAWF_USER_UID=0, closing an obvious privilege-escalation path in the container-identity-remap feature.setup-iptables.shresolves Squid viagetent/direct IP withis_valid_ipv4checkdomain-acl.ts:907-915assertSafeForSquidConfigLOGrules with--log-uid, rate-limited (setup-iptables.sh:808-824) + Squidfirewall_detailedlogformattmpfsoverlay hidesdocker-compose.yml(agent-service.ts:39-69)--limit 10/min --limit-burst 20on DROP-logging rulescapsh --drop=cap_sys_chroot,cap_sys_adminruns before user code (entrypoint.sh:1596-1635)disable_ipv6()(setup-iptables.sh:473-488) sets sysctl at container start🎯 Attack Surface Map
setup-iptables.shDNAT/filter chainsdisable_ipv6failure is non-fatal (only a WARNING), meaning IPv6 stays enabled if sysctl write fails, e.g. under gVisor/restricted/procentrypoint.shcapsh drop timing--allow-domainsCLI flag → squid.confSQUID_DANGEROUS_CHARSregex, fail-closed--allow-host-ports/--enable-host-accesshost.docker.internal/default route; falls back gracefully with warnings when unresolvednpm audit: 0 vulns across 652 deps📋 Evidence Collection
Commands run
✅ Recommendations
disable_ipv6()(setup-iptables.sh:473-488), a failedsysctl -w net.ipv6.conf.*.disable_ipv6=1only logs a WARNING and continues — consider making this fail-closed (abort container startup) since IPv6 remaining enabled directly defeats the documented rationale for disabling it (IPv4-only DNAT bypass prevention).npm audit --audit-level=high(or equivalent) on a schedule so dependency-introduced CVEs are caught automatically rather than only during manual reviews like this one.unconfinedtrade-off explicitly indocs/(it's currently only explained via inline code comments) so future maintainers don't remove the SYS_ADMIN-drop-before-exec ordering without understanding why unconfined AppArmor is otherwise safe.capsh --printinside a smoke test) rather than relying solely on static code review ofentrypoint.sh.📈 Security Metrics
host-iptables-rules.ts(340 lines),setup-iptables.sh(536 lines),entrypoint.sh(1700+ lines, capability-drop sections),domain-acl.ts(~980 lines combined evidence file),agent-service.ts(security config section),domain-validation.ts.npm audit).All reactions