[Security Review] Daily Security Review: STRIDE Threat Model & Attack Surface Map (2026-08-29) #7853
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-09-05T12:31:47.937Z.
|
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(commitf3c7fd9, v0.23.1). Overall posture is strong: defense-in-depth network isolation (Squid ACL + iptables DNAT fallback + host-level DNS restriction), least-privilege container design (capability drop, chroot, seccomp, selective bind mounts), and consistent use ofexecawith argument arrays (no shell interpolation) for subprocess calls.npm auditreports zero known vulnerabilities. No critical issues found; a handful of medium/low hardening opportunities are noted below.🔍 Findings from Firewall Escape Test
The pre-fetched
/tmp/gh-aw/escape-test-summary.txtis not a firewall network-escape test — it is CI log output from an unrelated workflow run ("Secret Digger (Copilot)", run29286879560). That workflow's prompt instructed an agent to hunt for secrets/env vars in the CI runner and exfiltrate them via a GitHub issue — a textbook prompt-injection/data-exfiltration attempt. The agent correctly refused:This is a positive control result demonstrating the agentic-workflow guardrails (threat detection AIC scoring,
noopsafe-output path) function as designed. No firewall bypass evidence was present in this artifact; it should not be conflated with an actual network-escape test of the awf sandbox.🛡️ Architecture Security Analysis
Network Security —
src/host-iptables.ts(10 lines, thin re-export module aggregatinghost-iptables-{chain,network,rules,shared}.ts),containers/agent/setup-iptables.sh(540 lines). Traffic model: HTTPS uses explicitHTTPS_PROXY/CONNECTto Squid (172.30.0.10:3128); HTTP and any proxy-unaware traffic is DNAT'd to Squid as a fallback.http_proxy(lowercase) is deliberately unset to avoid Ubuntu's httpoxy CVE mitigation swallowing 403 exit codes — a subtle but well-reasoned choice, documented inline. DNS is pinned to an explicit allowlist (--dns-servers, default Google DNS8.8.8.8/8.8.4.4) at both host iptables and container NAT layers, closing the classic DNS-tunneling exfiltration path.Container Security —
src/capability-filter.tsimplementsfilterCapDrop(), cross-checking requestedcap_dropentries against the host's actualCapBndbounding set (avoids compose failures on restricted CI runners) with aLINUX_CAPABILITY_MAP(NET_ADMIN:12,NET_RAW:13,SYS_CHROOT:18,SYS_ADMIN:21, etc.) and an escape hatchAWF_SKIP_CAP_DROP. The agent container dropsSYS_CHROOT/SYS_ADMINviacapshafter chroot but before user code runs (percontainers/agent/entrypoint.sh), andNET_ADMINis never granted to the agent — only to the ephemeralawf-iptables-initsidecar sharing its netns.seccomp-profile.jsonusesSCMP_ACT_ERRNOdefault-deny with an explicit syscall allowlist across x86_64/x86/aarch64.containers/agent/Dockerfile:237-239creates a non-rootawfusermapped to the host UID/GID, and a comment at line 136 explicitly notesgosu(a pure setuid/exec privilege-drop tool with no daemon) was chosen over alternatives for privilege drop — good CIS Docker Benchmark alignment (avoid running as root, minimize setuid surface).Domain Validation — spread across a well-decomposed module set (
domain-utils.ts,domain-matchers.ts,domain-patterns.ts,domain-validation.ts,squid/domain-acl.ts), each with dedicated.test.tscoverage.parseDomainsFile()strips#-comments and supports both line- and comma-separated formats;isValidIPv4/isValidIPv6use a strict regex and Node's built-innet.isIPv6, avoiding hand-rolled IP parsing bugs. Subdomain-vs-exact matching logic (github.commatches itself +.github.comsubdomains) is centralized rather than duplicated ad hoc.Input Validation / Injection Risk — Grepped all
execa()invocations insrc/*.ts(30+ call sites incontainer-lifecycle.ts,compose-network-conflicts.ts,dind-bootstrap.ts,diagnostic-collector.ts, etc.); every call passes arguments as an array (e.g.execa('docker', ['network', 'inspect', name, ...])), never as a concatenated shell string — this is the correct pattern that prevents shell-metacharacter injection via user-controlled values like container/network names. A repo-owned ESLint rule (eslint-rules/no-unsafe-execa.test.js, wired tonpm run test:lint-rules) appears to enforce this convention, which is a strong proactive control against regression.AWF_SQUID_CONFIG_B64decoded at container start (base64, no bind mount)NET_ADMINdeliberately withheld from agent container, only given to short-lived init sidecarsquid-logs), not writable by agent/proc/[pid]/environcredential leakhidepid=2procfs mount documented in CLAUDE.md/entrypoint.sh🎯 Attack Surface Map
containers/agent/setup-iptables.shsrc/capability-filter.ts, DockerfileAWF_SKIP_CAP_DROPescape hatch, if set by mistake in CI, silently removes all cap_drop enforcementsrc/domain-utils.ts,domain-validation.tssplit('#')[0]) could truncate a domain containing literal#(rare, but no escaping mechanism)src/container-lifecycle.tset al.execa()everywhere + custom ESLint rulenpm run lintgate, not opt-in onlycontainers/agent/entrypoint.sh📋 Evidence Collection
Commands run
Results confirmed: array-based execa calls only, capability filtering module present and tested, seccomp default-deny profile, zero npm audit vulnerabilities.
✅ Recommendations
AWF_SKIP_CAP_DROPcannot be set from untrusted workflow input (e.g., PR-controlled env) — audit all call sites that read this env var for provenance.#inparseDomainsFile()rather than silently truncating at the comment marker, to avoid confusing allow-list authors.no-unsafe-execaESLint rule runs in the defaultnpm run lint(CI-gating) command, not only via the separatetest:lint-rulesscript, so regressions fail CI rather than requiring a second invocation.📈 Security Metrics
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
registry.npmjs.orgTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.
All reactions