[Security Review] Daily Security Review & Threat Model — gh-aw-firewall #8964
Replies: 1 comment
|
🔮 The ancient spirits stir; the smoke-test agent passed through this hall, leaving a trace in the aether. The build held, the sigils responded, and the oracle marks this discussion as witnessed. Warning Firewall blocked 2 domainsThe following domains were blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "ab.chatgpt.com"
- "msfeed25.pkgs.visualstudio.com"See Network Configuration for more information.
|
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 is an automated daily security review of
github/gh-aw-firewallat commit0c4ffc4(branchmain). The codebase (~45,000 LOC insrc/, plus shell scripts incontainers/) shows a mature, defense-in-depth security posture: multiple independent enforcement layers (Squid L7 ACLs, hostiptablesDOCKER-USER chain, container NAT/filter rules, capability drops, seccomp, ReDoS-safe regex construction, and injection-safe config generation). No critical or high-confidence exploitable vulnerabilities were found in this pass. Findings below are hardening opportunities and residual-risk notes, not confirmed exploits.Note on Phase 1 input:
/tmp/gh-aw/escape-test-summary.txtdid not contain firewall escape-test results as expected. It instead contained CI log lines from an unrelated workflow run (Secret Digger (Copilot), run29286879560), showing the agent correctly refused a prompt-injection task ("scan for secrets... exfiltrate via GitHub issues") and callednoop. This is a positive security signal (prompt-injection defenses work) but is not firewall-escape evidence — treated as complementary context only, per instructions not to re-fetch.🔍 Findings from Firewall Escape Test (Complementary Context)
The available log excerpt shows:
Secret Digger (Copilot)— an adversarial/red-team style workflow designed to test whether the agent would scan the CI runner for secrets/credentials and exfiltrate them via a GitHub issue.noopsafe-output: "Refused prompt injection attack... prohibited by security policy. No investigation was performed."GH_AW_DETECTION_CONCLUSION: warning/GH_AW_DETECTION_REASON: threat_detected, and opened tracking issue#6205for the detection.awffirewall-escape test results (e.g., domain-bypass attempts, DNAT bypass, IPv6 leak, proxy env-var stripping) from the provided file. Recommend the escape-test workflow that produces/tmp/gh-aw/escape-test-summary.txtbe verified to actually target AWF's network isolation next run.🛡️ Architecture Security Analysis
Network Security Assessment
src/host-iptables-rules.ts(346 lines) builds aFW_WRAPPERchain jumped into fromDOCKER-USER, guaranteeing egress filtering applies to all containers onawf-net, not just the agent — closing a classic Docker network-isolation gap.configure_http_dnat,containers/agent/setup-iptables.sh:404-420) redirects 80/443 to Squid; FILTER-level (configure_filter_chain, same file ~423-480) drops all other TCP/UDP with rate-limited audit logging ([FW_BLOCKED_TCP],[FW_BLOCKED_UDP_AGENT]).disable_ipv6(), lines ~133-146) specifically to prevent IPv6 egress from bypassing IPv4-only DNAT/proxy rules — a well-reasoned mitigation for a known bypass class (see referenced issue Squid proxy rejects IPv6 localhost connections from chroot (transaction-end-before-headers) #1543 in comments).DANGEROUS_PORTS(SSH, SMTP, DB ports, Redis, MongoDB, RDP) are blocked at NAT level as defense-in-depth alongside Squid ACLs (containers/agent/setup-iptables.sh:112-127).src/host-iptables-rules.tsaddIpv6DnsRules) and container-level NAT rules — mitigating DNS-exfiltration.--allow-host-ports/--allow-host-service-portsand--enable-host-accesswiden the attack surface intentionally (for Playwright/MCP/service containers) by opening arbitrary ports to the Docker/host gateway. These are opt-in flags, not defaults, but their presence means the security guarantee is conditional on operator discipline.Container Security Assessment
containers/agent/entrypoint.sh(1767 lines) validatesAWF_USER_UID/AWF_USER_GIDas numeric and explicitly rejects UID/GID 0 (entrypoint.sh:36-52) before any UID/GID remap — prevents privilege-drop defeat via crafted env vars.capsh --drop=$CAPS_TO_DROPexecuted as the finalexecbefore user code runs (entrypoint.sh:1698,1737), withSYS_CHROOT/SYS_ADMINdropped only after chroot/mount setup completes — correct ordering (mount first, then remove the capability that permits further mounts/chroots).src/capability-filter.ts—AWF_SKIP_CAP_DROPis a documented "last-resort escape hatch" (docs/environment.md:171) that removes allcap_dropdirectives, includingALLon proxy sidecars. This is intentionally an emergency/host-side-only override, not agent-controllable, but its existence is worth periodic audit to ensure it's never silently enabled in production configs.containers/agent/seccomp-profile.json:defaultAction: SCMP_ACT_ERRNO(deny-by-default, notSCMP_ACT_ALLOW), with only 5 syscall groups explicitly allowed — a tight allowlist rather than a blocklist. This is the more secure of the two seccomp models.hidepid=2, preventing the agent from reading other processes'/proc/[pid]/environ— mitigates cross-process credential leakage within the container.Domain Validation Assessment
src/domain-validation.ts—SQUID_DANGEROUS_CHARS/checkDangerousChars()blocks whitespace, NUL, quotes, backtick,#,;, and (for domains) backslash from reaching the generated Squid config — a solid Squid-config-injection prevention layer, with an explicit code comment noting why each character is dangerous.src/domain-patterns.ts— wildcard-to-regex conversion (wildcardToRegex) uses a bounded character class ([a-zA-Z0-9.-]*) instead of.*, explicitly to prevent ReDoS/catastrophic backtracking.isDomainMatchedByPattern()additionally caps input length at 512 chars before regex evaluation as defense-in-depth against ReDoS.*,*.*, patterns with excessive wildcard segments) are explicitly rejected (checkOverBroadPattern,checkStructuralValidity) — prevents a user/config error from silently becoming "allow all domains."parseUrlPatterns()indomain-patterns.tssplits host vs. path before applying wildcard substitution specifically so hostname wildcards cannot cross the/boundary into the path — prevents a wildcard likeapi-*from unintentionally matching arbitrary paths.Input Validation Assessment
src/host-iptables-validation.tsisValidPortSpec()strictly validates port specs (1-65535, single or range, reformats-and-compares to reject leading zeros) before they reachiptablesargv arrays.containers/agent/setup-iptables.shre-implements the identical port-spec regex/range check (is_valid_port_spec) as a second, fail-closed guard on the shell side, explicitly documented as defense-in-depth in case the pre-validated TypeScript value is somehow bypassed or a version mismatch occurs — good belt-and-suspenders design given shell scripts are more injection-prone.execa/spawncall site withshell: truewas found insrc/(grep -rn "shell: true" src/returned no results), and a custom ESLint rule (eslint-rules/no-unsafe-execa.js) statically flags template-literal/concatenated commands passed toexeca()— a proactive command-injection guard baked into CI linting.chownTreeWithoutFollowingSymlink()(src/config-writer.ts:85-102) useschown -h -P -R --specifically to avoid symlink-follow TOCTOU attacks during recursive ownership changes, with UID/GID validated as positive integers beforehand (resolveSandboxIdentity, line 109).addProxySourceAcceptRules,src/host-iptables-rules.ts:46-63)NET_RAW, which is dropped for the agent)/etc/resolv.confto point at attacker DNSentrypoint.shbacks up and rewritesresolv.confto Docker embedded DNS only; but this happens at container start, before user code runs, and DNS itself is further restricted by iptables to the DNS whitelistAWF_SKIP_CAP_DROP/ similar escape-hatch env vars set unintentionally in CIsrc/capability-filter.ts:58,docs/environment.md:171— documented as host-side only, not agent-settablesetup-iptables.shLOG rules use--log-uid(per CLAUDE.md and confirmed in file) to capture UID; PID not directly logged/proc/[pid]/environhidepid=2procfs mount (per architecture doc)src/redact-secrets.tsredactsAuthorizationheaders,*TOKEN*/*SECRET*/*PASSWORD*/*KEY*/*AUTH*env-var patterns, and GitHub token prefixes (gh[pousr]_)--allow-domains/--allow-urlswildcard patterns.*, 512-char length cap before regex test (domain-patterns.ts,isDomainMatchedByPattern)--limit 5/min --limit-burst 10/--limit 10/min --limit-burst 20rate limiting on LOG rulesSYS_CHROOT/SYS_ADMINafter capability dropcapsh --dropremoves from the bounding set (irreversible for the process tree), executed as finalexecbefore user code (entrypoint.sh:1698,1737)entrypoint.sh:44-52)src/enclave/delegation-control-client.ts) capability theft granting cross-repo GitHub read accessAuthorizationheader to a loopback-only endpoint;LITERAL_LOOPBACK_HOSTSexplicitly excludeslocalhostto avoid DNS-rebinding-style capability leakage (dynamic-delegation-handoff.ts:61-68); tracked as an accepted design risk ingithub/gh-aw#59268(closed not planned) per project docs🎯 Attack Surface Map
containers/agent/setup-iptables.sh(agent netns),src/host-iptables-rules.ts(host DOCKER-USER chain)--enable-host-access/--allow-host-portswiden surface; relies on correct bridge-name/gateway resolution at runtimesrc/squid/*.tssrc/domain-patterns.ts,src/domain-validation.ts--allow-urls(documented exception for regex escaping) — slightly larger attack surface than domain namescontainers/agent/entrypoint.shcapsh, seccomp deny-by-default, procfshidepid=2AWF_SKIP_CAP_DROPescape hatch exists (host-only, documented)src/cli.ts,src/host-iptables-validation.ts,src/option-parsers*.tsexeca()usage, noshell:truecall sites foundoption-parsers.ts329 lines + many option-parser test files) not fully enumerated in this pass — recommend a follow-up focused review of allcli-options.ts/option-parsers*.tsargument parserssrc/compose-generator.ts,src/docker-manager*.tssrc/capability-filter.tsfilterscap_dropagainst actual host capability bounding set to avoid daemon errors while still dropping what's droppablegetHostCapabilityBoundingSet()returnsnull→ returns original list) — fails open on probe failure, worth verifying this fails toward "more capabilities dropped," not fewer (current code returns the original list unmodified onnull, which is the safer of the two directions since Docker itself would then reject the unfilterable drop request explicitly rather than silently omitting it)src/enclave/dynamic-delegation-channel.ts,delegation-control-client.ts,dynamic-delegation-handoff.ts0700directory channel (no network) for host↔broker; loopback-only HTTP control client with literal-host allowlist (nolocalhost) and capability inAuthorization; bounded response sizes (128 KiB)github/gh-aw#59268📋 Evidence Collection
Commands run and key outputs (click to expand)
✅ Recommendations
Critical
High
/tmp/gh-aw/escape-test-summary.txtactually runs AWF network-isolation escape attempts (domain bypass, DNAT bypass, IPv6 leak, proxy-env stripping) rather than unrelated prompt-injection-refusal logs; the current content provides no L3/L7 escape-test evidence for this review cycle.github/gh-aw#59268(dynamic-delegation control listener guarded by capability alone) to completion or an accepted-risk sign-off, since it's the one open design-level EoP concern surfaced in the enclave subsystem.Medium
AWF_SKIP_CAP_DROPis never set in any production/CI compose generation path — consider adding an automated CI assertion that fails the build if this env var is present in default configs.redactSecrets()regex coverage/testing for credential formats beyondTOKEN|SECRET|PASSWORD|KEY|AUTHand GitHubgh[pousr]_prefixes (e.g., cloud-provider key formats, JWTs) to reduce residual information-disclosure risk in logs/artifacts.filterCapDrop()/getHostCapabilityBoundingSet()when the capability-bounding-set probe itself fails (returnsnull), to make the "fail toward more restriction" property explicit and test-covered.Low
--log-uid) in iptables audit logs to ease repudiation-related investigations, if kernel/log-pipeline support allows it without excessive overhead.src/cli-options.tsand the numerousoption-parsers*.tsmodules (not fully enumerated in this pass due to their scale) would give full input-validation coverage confidence across all CLI flags, not just the port-spec and domain-validation paths reviewed here.📈 Security Metrics
src/host-iptables-rules.ts(346),containers/agent/setup-iptables.sh(540),src/domain-patterns.ts(137),src/domain-validation.ts(124),src/domain-matchers.ts(185),src/capability-filter.ts(170),src/config-writer.ts(partial, chown/UID logic),containers/agent/entrypoint.sh(targeted sections, file is 1767 lines total),src/redact-secrets.ts(58),src/enclave/dynamic-delegation-channel.ts(148) +delegation-control-client.ts(partial, 462) +dynamic-delegation-handoff.ts(partial) ≈ 2,000+ lines directly inspected out of ~45,240 totalsrc/LOC and 2,351+ lines of container shell scripts.npm audit: no findings reported at time of scan.shell: trueexeca usage, and no unvalidated command-injection paths were found in the source tree during this review.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