[Security Review] Daily Security Review: 2026-09-10 — Network isolation, capability model, injection resistance #8389
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-09-17T12:38:53.200Z.
|
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
Daily automated security review of
github/gh-aw-firewall. Overall security posture is strong: defense-in-depth network isolation (Squid L7 ACL + iptables L3/L4 + host-level DOCKER-USER chain), disciplined capability dropping (NET_ADMINnever granted to the agent container;SYS_CHROOT/SYS_ADMINdropped viacapshbefore user code runs), safe subprocess execution (execawith argument arrays, noshell:trueanywhere), and careful shell-quoting of user commands (printf '%q ') beforechroot+capshexec.npm auditreports 0 known vulnerabilities. No critical issues were found in this cycle; a handful of medium/low hardening opportunities are listed below.🔍 Findings from Firewall Escape Test
The pre-fetched escape-test artifact (
/tmp/gh-aw/escape-test-summary.txt) is a workflow-run log for a different workflow ("Secret Digger (Copilot)", run29286879560), not an AWF network-escape test transcript. Evidence:Positive finding: the agent correctly refused a prompt-injection attempt to exfiltrate secrets/credentials via a GitHub issue and called
noop, and the harness's independent threat-detection layer separately flaggedthreat_detectedand logged it to issue #6205. This is the injection-resistance safety net working as designed — it is complementary to, not a replacement for, network-layer escape testing. No AWF container-escape or Squid/iptables-bypass attempt data was present in this artifact, so no network-security conclusions were drawn from it.🛡️ Architecture Security Analysis
Network Security
containers/agent/setup-iptables.sh(540 lines) implements layered NAT+filter rules: DNAT of ports 80/443 to Squid (configure_http_dnat), an explicit NAT blacklist of dangerous ports (22, 23, 25, 110, 143, 445, 1433, 1521, 3306, 3389, 5432, 6379, 27017, 27018, 28017 — lines ~113-129) that RETURN from NAT and are dropped by the filter chain's default-deny, and a finaliptables -A OUTPUT -p tcp/udp -j DROP(default deny,configure_filter_chain).disable_ipv6()) specifically to prevent egress paths (including::1) that bypass the IPv4-only DNAT rules — a good defensive design decision, with rationale documented inline and cross-referenced togithub/gh-aw-firewall#1543.AWF_DNS_SERVERS(default8.8.8.8,8.8.4.4) plus Docker's embedded resolver127.0.0.11; direct UDP/TCP to arbitrary DNS servers is blocked by the default-deny UDP DROP rule, mitigating DNS-based exfiltration.--allow-host-ports/--allow-host-service-portsport specs are parsed and validated twice (TypeScriptparseValidPortSpecs()/isValidPortSpec()pre-validates; the shellis_valid_port_spec()insetup-iptables.shre-validates as a fail-closed defense-in-depth check) — both explicitly reject dangerous ports even when user-supplied (src/squid/validation.ts::validateAndSanitizeHostAccessPort).src/domain-patterns.ts) converts wildcard patterns to anchored, ReDoS-safe regexes using a bounded[a-zA-Z0-9.-]*character class instead of.*, explicitly to avoid catastrophic backtracking — a subtle but important hardening detail.src/squid/access-rules.ts::assertSafeForSquidConfig()rejects whitespace/quotes/semicolons/backticks/hashes before interpolating any domain/pattern string intosquid.conf, preventing Squid-config injection via crafted--allow-domainsvalues.Container Security
cap_add: ['SYS_CHROOT','SYS_ADMIN'],cap_dropincludesNET_RAWand others (src/services/agent-service.ts:80-90);NET_ADMINis never granted to the agent — it is isolated to a separateawf-iptables-initcontainer (cap_add: ['NET_ADMIN','NET_RAW'],cap_drop: ['ALL']) that shares only the network namespace and exits before the user command starts (src/services/agent-service.ts:365-368). This is verified by dedicated tests (agent-service-build.test.ts:100-118).capsh --drop=${CAPS_TO_DROP}dropsSYS_CHROOT/SYS_ADMINimmediately beforeexec'ing the user's command inside the chroot (entrypoint.sh:1698), closing the privilege window.containers/agent/seccomp-profile.json) usesdefaultAction: SCMP_ACT_ERRNO(default-deny) with a small explicit allow-list.cap_drop: ['ALL']andno-new-privileges:truevia the sharedservice-security.tshelper.Domain Validation
wildcardToRegex()anchors patterns (^...$) and escapes all regex metacharacters except*, which maps to a bounded character class — prevents both regex-injection and ReDoS.Input Validation / Injection Risks
execa/execa.synccalls acrosssrc/*.tspass arguments as arrays (e.g.execa('docker', ['network','inspect', name, ...])), never string-interpolated shell commands — grep found zero occurrences ofshell: trueinsrc/orcontainers/.printf '%q 'before being embedded in thebash -c "..."string (entrypoint.sh:1548,1698,1737), which is the correct pattern for safely re-quoting arbitrary argv into a shell command string.validateProxyIp,is_valid_ipv4) and port validation are applied consistently on both the TypeScript and shell sides for values that flow intoiptablesinvocations.AWF_API_PROXY_IP/AWF_CLI_PROXY_IPare trusted inputs to iptables rules (setup-iptables.shconfigure_nat_bypasses) but are set bydocker-manager.ts, not attacker-controlled at agent runtimeNET_ADMINnever granted to agent; iptables-init exits before user code runsdump_audit_state()writes NAT/filter rules to/run/awf-init/iptables-audit.txt; Squidfirewall_detailedlogformat captures full request metadata/prochidepid=2(per CLAUDE.md);/etc/shadowexcluded from bind mounts-m limit --limit 5/min --limit-burst 10/10/min --limit-burst 20rate-limits on LOG rules (configure_filter_chain)SYS_CHROOT/SYS_ADMINdropped viacapshbefore user code exec; seccomp default-deny;cap_dropincludesNET_RAW🎯 Attack Surface Map
containers/agent/setup-iptables.sh(iptables-init)/run/awf-init/readysignal) — a race here could theoretically leave a brief unprotected window, though design mitigates viadepends_on/readiness signalsrc/squid/domain-acl.ts,src/domain-patterns.tsassertSafeForSquidConfig)parseDomainList/isDomainMatchedByPatterndedup logic is security-relevant and worth continued fuzzingsrc/services/agent-service.ts,entrypoint.shcapsh dropNET_ADMINisolated to init container;SYS_CHROOT/SYS_ADMINdropped before user codeagent-service-build.test.ts) required in CIsrc/container-lifecycle.ts,src/artifact-*.tsexecacalls only, noshell:truecontainers/agent/entrypoint.sh(1767 lines)printf '%q 'quoting of user argv beforebash -c📋 Evidence Collection
Commands run
Key outputs
npm audit --json→{"vulnerabilities": {}}(0 findings)grep -rn "shell: true" src/ containers/→ no matchescontainers/agent/seccomp-profile.json→defaultAction: SCMP_ACT_ERRNO, 5 syscall allow entriesagent-service-build.test.ts:100-118assertsNET_ADMINis absent from the agent'scap_addand present only on theiptables-initservice✅ Recommendations
agent-service-build.test.ts/agent-security-config.test.tscapability-drop assertions in CI so future refactors cannot silently regress theNET_ADMINisolation orSYS_CHROOT/SYS_ADMINdrop sequence./run/awf-init/ready) is always honored before the agent's user command begins, to close any theoretical startup race window.containers/agent/entrypoint.shis very large (1767 lines) and handles multiple security-critical concerns (privilege drop, mounts, script templating) in one file; consider splitting privilege-drop/mount logic into separately unit-testable modules to reduce audit surface over time./tmp/gh-aw/escape-test-summary.txtwas from an unrelated "Secret Digger" prompt-injection test) to get complementary live egress-bypass evidence next cycle.📈 Security Metrics
src/host-iptables.ts,containers/agent/setup-iptables.sh(540 lines),src/squid/*.ts(domain-acl, validation, access-rules, config-generator — ~1000+ lines combined),src/domain-patterns.ts(137 lines),src/services/agent-service.ts,containers/agent/entrypoint.sh(1767 lines),containers/agent/seccomp-profile.json.npm audit).Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
msfeed25.pkgs.visualstudio.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.
All reactions