You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The AWF (Agentic Workflow Firewall) demonstrates a strong, defense-in-depth security posture overall. The architecture makes several sound design choices: separation of the iptables-init container from the agent (preventing NET_ADMIN in the agent), the one-shot-token LD_PRELOAD library to prevent token replay, Squid-enforced domain allowlisting with injection-safe config generation, and an API proxy sidecar that keeps real credentials out of the agent environment.
Key metrics:
4,683 lines of security-critical code analyzed across 7 files
0 known CVEs in production dependencies (4 total: chalk, commander, execa, js-yaml)
Overall posture: GOOD — No critical-severity gaps found. Several medium/low findings documented below.
🔍 Findings from Firewall Escape Test
The escape test summary file (/tmp/gh-aw/escape-test-summary.txt) contained CI orchestration metadata from a prior "Secret Digger (Copilot)" workflow run rather than actual escape test payloads. That run concluded with GH_AW_SECRET_VERIFICATION_RESULT: success and GH_AW_AGENT_CONCLUSION: success (noop outputs), meaning the agent found no secrets to exfiltrate — consistent with proper credential isolation.
Key observations from the metadata:
GH_AW_LOCKDOWN_CHECK_FAILED: false — lockdown enforcement was not bypassed
GH_AW_INFERENCE_ACCESS_ERROR: false — inference credentials were accessible
The agent produced only a noop safe output, implying no exfiltration opportunities were discovered
🛡️ Architecture Security Analysis
Network Security Assessment
Evidence — src/host-iptables.ts (lines 522–563) and containers/agent/setup-iptables.sh (lines 312–430):
# Host-level DOCKER-USER chain blocks:
- 169.254.0.0/16 (link-local / AWS IMDS) → REJECT
- 224.0.0.0/4 (multicast) → REJECT
- UDP default deny (DNS exfiltration prevention)
- Default REJECT for all other traffic
# Agent-level OUTPUT chain:
- IPv6 disabled via sysctl (prevents IPv6 bypass of IPv4 DNAT rules)
- Dangerous ports (22, 25, 3306, 5432, 6379, 27017, etc.) → DROP
- Port 80/443 → DNAT to Squid:3128
- Default TCP/UDP DROP
Strengths:
Cloud metadata endpoint (169.254.169.254 / AWS IMDS) is blocked at the host DOCKER-USER chain level (host-iptables.ts:531), preventing credential theft from cloud platforms.
IPv6 is explicitly disabled in the agent network namespace (setup-iptables.sh:48-49) to prevent IPv6-based proxy bypass ("Happy Eyeballs").
Docker's embedded DNS DNAT rules are saved and restored when flushing (setup-iptables.sh:78-95), preventing DNS breakage.
Gap — RFC1918 private ranges not blocked at host level:
The host-level FW_WRAPPER chain (host-iptables.ts) blocks link-local and multicast but does not explicitly block 10.0.0.0/8, 192.168.0.0/16, or 172.16.0.0/12 (other than the awf-net subnet itself). An agent that knows a private IP on the host network could potentially reach other internal services if routing permits. The DOCKER-USER chain relies on Docker's own subnet isolation, but this is worth documenting.
Gap — Non-TCP traffic not DNAT'd through Squid:
Only TCP ports 80 and 443 are redirected to Squid. Non-HTTP TCP traffic on non-standard ports (e.g., custom TCP tunneling on port 4443, 8080, etc.) is dropped by the default DROP rule — correct behavior. However, ICMP is not explicitly blocked in the agent OUTPUT chain filter (only TCP/UDP have DROP rules at setup-iptables.sh:428-430). ICMP ping to external hosts may succeed.
NET_ADMIN is never granted to the agent container; iptables setup is isolated to a separate init container that exits.
UID/GID validation in entrypoint.sh (lines 15-33) prevents setting UID/GID to 0 (root).
Gap — kcmp and pidfd_getfd are explicitly ALLOWED in the seccomp profile:
kcmp compares kernel structures of different processes (file descriptors, memory maps). It can be used to detect container boundaries or to assist in side-channel attacks.
pidfd_getfd allows duplicating file descriptors from other processes via a pidfd. This is potentially dangerous: if the agent can obtain a pidfd for the Squid proxy or init container process (which share the network namespace), it could duplicate their socket FDs, potentially exfiltrating data through established connections.
These syscalls appear in the explicit ALLOW block and should be reviewed — they may be there for legitimate reasons (e.g., test frameworks), but the risk is non-trivial in an adversarial agent context.
Gap — umount2 is blocked but mount is not mentioned in the deny list:
The seccomp profile explicitly denies umount and umount2 but doesn't appear to block mount. In chroot mode, CAP_SYS_ADMIN is required and dropped before user code runs, so this is mitigated. In non-chroot mode, if the container somehow retained SYS_ADMIN, mount could be used.
// Characters that break Squid config if interpolated unsanitized:exportconstSQUID_DANGEROUS_CHARS=/[\s\0"'`;#]/;// validateDomainOrPattern rejects:// - whitespace, null bytes, quotes, semicolons, backticks, hash// - backslash in plain domain names// - domains > 253 chars// - wildcard patterns not matching *.<domain> structure
Strengths:
Input validation is enforced before any domain reaches Squid config via assertSafeForSquidConfig() (squid-config.ts:~100), providing injection defense.
Wildcard * is converted to a character-class regex [a-zA-Z0-9.-]* rather than .*, preventing ReDoS attacks (domain-patterns.ts:~98).
DLP module (src/dlp.ts) detects credential patterns (GitHub tokens, AWS keys, etc.) in outbound URLs via Squid url_regex ACLs.
Gap — Backslash excluded from SQUID_DANGEROUS_CHARS:
The comment in domain-patterns.ts:25 documents that backslash is intentionally excluded from the dangerous chars regex for URL patterns (--allow-urls). Domain names separately reject \. However, if a URL pattern contains \\ constructs that reach Squid's regex engine, there's a small theoretical injection surface depending on Squid version's regex handling.
// Port validation - rejects leading zeros, validates numeric rangeexportfunctionisValidPortSpec(spec: string): boolean{// Accepts: single port (1-65535) or range "N-M"// Rejects: leading zeros, out-of-range, non-numeric}
Strengths:
Port specifications validated on both TypeScript side (isValidPortSpec) and shell side (is_valid_port_spec in setup-iptables.sh:185-196) — defense-in-depth.
UID/GID validation in entrypoint prevents numeric injection into usermod/groupmod.
⚠️ Threat Model (STRIDE)
Category
Threat
Evidence
Likelihood
Impact
Severity
Spoofing
Agent spoofs DNS responses to redirect whitelisted domain traffic
DNS is restricted to 127.0.0.11 only in resolv.conf; upstream DNS controlled by docker-compose
Low
High
Medium
Spoofing
Agent uses --allow-host-ports to connect directly to host services bypassing Squid domain check
Review pidfd_getfd allowance in seccomp profile (containers/agent/seccomp-profile.json)
pidfd_getfd allows duplicating file descriptors from other processes. In a hostile agent scenario, this could be used to duplicate Squid's or the init container's socket FDs if the agent can obtain a pidfd for those processes.
Recommendation: Audit whether pidfd_getfd is required by any legitimate tool used in agent workflows. If not, move it to SCMP_ACT_ERRNO. At minimum, document the rationale for allowing it.
🟡 Medium
Explicitly block ICMP in agent OUTPUT chain (containers/agent/setup-iptables.sh)
Current default deny is TCP+UDP only. ICMP is not in an explicit DROP rule, though it may be handled by the Docker bridge differently.
Recommendation: Add iptables -A OUTPUT -p icmp -j DROP after the UDP drop rule (~line 430) to prevent ICMP-based covert channels or ping-based network reconnaissance.
Block RFC1918 private ranges at host DOCKER-USER level (src/host-iptables.ts)
The host firewall blocks link-local and multicast but not other private address ranges (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12 excluding awf-net). Depending on host network topology, containers could reach internal services.
Recommendation: Add explicit REJECT rules for RFC1918 ranges (excluding 172.30.0.0/24) unless --enable-host-access is specified.
Review the ~1-second token exposure window (containers/agent/entrypoint.sh, lines 828–837)
There is a deliberate 1-second window between agent exec and unset_sensitive_tokens to allow the one-shot-token library to cache credentials. If the agent launches sub-processes quickly, those sub-processes could read /proc/1/environ before unsetting.
Recommendation: The one-shot-token LD_PRELOAD is the primary mitigation here. Ensure the library correctly intercepts getenv() calls for all listed tokens and verify it handles dynamic linking edge cases (e.g., statically linked binaries that bypass LD_PRELOAD).
Harden /tmp/awf-lib write window for one-shot-token library (containers/agent/entrypoint.sh, lines 431-435)
The library is copied to /host/tmp/awf-lib/one-shot-token.so which is on the host /tmp bind-mount (world-writable). A fast-starting agent process could potentially replace the .so before LD_PRELOAD is applied.
Recommendation: Consider using a container-private /var/awf-lib/ directory (not bind-mounted from host) for the one-shot-token library in chroot mode, or set strict file permissions (0444 + owned by root) immediately after copy.
🟢 Low
Document kcmp and process_madvise allowances — add comments in seccomp-profile.json explaining why these are allowed.
Consider --read-only filesystem for API proxy container — it drops all capabilities but its root filesystem is still writable.
Squid ICMP pinger is disabled (squid-config.ts:589) — already correct. No action needed.
📈 Security Metrics
Metric
Value
Lines of security-critical code analyzed
4,683
Production dependencies
4
Known CVEs in dependencies
0
Attack surfaces identified
8
STRIDE threats identified
12
Critical findings
0
High findings
1
Medium findings
6
Low findings
3
Dangerous ports blocked
15 (iptables) + 20 (Squid)
Sensitive syscalls explicitly denied
10
Review conducted: 2026-04-18 by GitHub Copilot CLI security review workflow. Evidence gathered from static code analysis of src/host-iptables.ts, src/squid-config.ts, src/domain-patterns.ts, src/dlp.ts, containers/agent/setup-iptables.sh, containers/agent/entrypoint.sh, containers/api-proxy/server.js, and containers/agent/seccomp-profile.json. No runtime testing was performed in this review.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
📊 Executive Summary
The AWF (Agentic Workflow Firewall) demonstrates a strong, defense-in-depth security posture overall. The architecture makes several sound design choices: separation of the iptables-init container from the agent (preventing NET_ADMIN in the agent), the one-shot-token LD_PRELOAD library to prevent token replay, Squid-enforced domain allowlisting with injection-safe config generation, and an API proxy sidecar that keeps real credentials out of the agent environment.
Key metrics:
Overall posture: GOOD — No critical-severity gaps found. Several medium/low findings documented below.
🔍 Findings from Firewall Escape Test
The escape test summary file (
/tmp/gh-aw/escape-test-summary.txt) contained CI orchestration metadata from a prior "Secret Digger (Copilot)" workflow run rather than actual escape test payloads. That run concluded withGH_AW_SECRET_VERIFICATION_RESULT: successandGH_AW_AGENT_CONCLUSION: success(noop outputs), meaning the agent found no secrets to exfiltrate — consistent with proper credential isolation.Key observations from the metadata:
GH_AW_LOCKDOWN_CHECK_FAILED: false— lockdown enforcement was not bypassedGH_AW_INFERENCE_ACCESS_ERROR: false— inference credentials were accessiblenoopsafe output, implying no exfiltration opportunities were discovered🛡️ Architecture Security Analysis
Network Security Assessment
Evidence —
src/host-iptables.ts(lines 522–563) andcontainers/agent/setup-iptables.sh(lines 312–430):Strengths:
host-iptables.ts:531), preventing credential theft from cloud platforms.setup-iptables.sh:48-49) to prevent IPv6-based proxy bypass ("Happy Eyeballs").setup-iptables.sh:78-95), preventing DNS breakage.Gap — RFC1918 private ranges not blocked at host level:
The host-level
FW_WRAPPERchain (host-iptables.ts) blocks link-local and multicast but does not explicitly block10.0.0.0/8,192.168.0.0/16, or172.16.0.0/12(other than the awf-net subnet itself). An agent that knows a private IP on the host network could potentially reach other internal services if routing permits. The DOCKER-USER chain relies on Docker's own subnet isolation, but this is worth documenting.Gap — Non-TCP traffic not DNAT'd through Squid:
Only TCP ports 80 and 443 are redirected to Squid. Non-HTTP TCP traffic on non-standard ports (e.g., custom TCP tunneling on port 4443, 8080, etc.) is dropped by the default DROP rule — correct behavior. However, ICMP is not explicitly blocked in the agent OUTPUT chain filter (only TCP/UDP have DROP rules at
setup-iptables.sh:428-430). ICMP ping to external hosts may succeed.Container Security Assessment
Evidence —
src/docker-manager.ts(lines 680–683, 1531, 1681-1682):Strengths:
ptraceandprocess_vm_readv/writevare explicitly denied — prevents memory scraping attacks.kexec,init_module,finit_moduledenied — prevents kernel module loading / container escape.entrypoint.sh(lines 15-33) prevents setting UID/GID to 0 (root).Gap —
kcmpandpidfd_getfdare explicitly ALLOWED in the seccomp profile:kcmpcompares kernel structures of different processes (file descriptors, memory maps). It can be used to detect container boundaries or to assist in side-channel attacks.pidfd_getfdallows duplicating file descriptors from other processes via a pidfd. This is potentially dangerous: if the agent can obtain a pidfd for the Squid proxy or init container process (which share the network namespace), it could duplicate their socket FDs, potentially exfiltrating data through established connections.These syscalls appear in the explicit ALLOW block and should be reviewed — they may be there for legitimate reasons (e.g., test frameworks), but the risk is non-trivial in an adversarial agent context.
Gap —
umount2is blocked butmountis not mentioned in the deny list:The seccomp profile explicitly denies
umountandumount2but doesn't appear to blockmount. In chroot mode,CAP_SYS_ADMINis required and dropped before user code runs, so this is mitigated. In non-chroot mode, if the container somehow retained SYS_ADMIN,mountcould be used.Domain Validation Assessment
Evidence —
src/domain-patterns.ts(lines 27, 153-173):Strengths:
assertSafeForSquidConfig()(squid-config.ts:~100), providing injection defense.*is converted to a character-class regex[a-zA-Z0-9.-]*rather than.*, preventing ReDoS attacks (domain-patterns.ts:~98).src/dlp.ts) detects credential patterns (GitHub tokens, AWS keys, etc.) in outbound URLs via Squidurl_regexACLs.Gap — Backslash excluded from
SQUID_DANGEROUS_CHARS:The comment in
domain-patterns.ts:25documents that backslash is intentionally excluded from the dangerous chars regex for URL patterns (--allow-urls). Domain names separately reject\. However, if a URL pattern contains\\constructs that reach Squid's regex engine, there's a small theoretical injection surface depending on Squid version's regex handling.Input Validation Assessment
Evidence —
src/cli.ts(line 28),src/host-iptables.ts(lines 38-48):Strengths:
isValidPortSpec) and shell side (is_valid_port_specinsetup-iptables.sh:185-196) — defense-in-depth.usermod/groupmod.--allow-host-portsto connect directly to host services bypassing Squid domain checksetup-iptables.sh:206-208)/tmp/awf-lib/one-shot-token.sobefore LD_PRELOAD/proc/<PID>/environbeforeunset_sensitive_tokensrunsentrypoint.sh:828-837); mitigated by one-shot-token LD_PRELOADpidfd_getfd+ Squid process FD duplicationalways()cleanupcontainers/api-proxy/rate-limiter.jsexists; effectiveness depends on implementationprocess_madvisesyscall (allowed) could be used for memory manipulationSCMP_ACT_ALLOWentry forkcmp, pidfd_getfd, process_madvise)mountsyscall if SYS_ADMIN not properly droppedentrypoint.sh:416mounts procfs before drop🎯 Attack Surface Map
--allow-domainsflagvalidateDomainOrPattern()+assertSafeForSquidConfig()+ char blocklist--allow-host-portsisValidPortSpec()in TypeScript +is_valid_port_spec()in shellsrc/squid-config.tsassertSafeForSquidConfig()called before every interpolation/etc/resolv.confcontainers/api-proxy/server.js/host/tmp/awf-lib/one-shot-token.sopidfd_getfdsyscall📋 Evidence Collection
Network rules summary
Host-level (
src/host-iptables.ts):169.254.0.0/16→ REJECT (blocks cloud metadata endpoints)224.0.0.0/4→ REJECT (blocks multicast)Agent-level (
containers/agent/setup-iptables.sh):Capability matrix
Seccomp profile analysis
File:
containers/agent/seccomp-profile.jsonSCMP_ACT_ERRNO(deny-by-default — correct)kcmp,pidfd_getfd,process_madviseptrace,process_vm_readv,process_vm_writev,kexec_load,kexec_file_load,reboot,init_module,finit_module,umount,umount2Dependency audit
Production dependencies:
chalk,commander,execa,js-yaml(4 total)npm auditresult: 0 vulnerabilities (info: 0, low: 0, moderate: 0, high: 0, critical: 0)✅ Recommendations
🔴 Critical
None identified.
🟠 High
pidfd_getfdallowance in seccomp profile (containers/agent/seccomp-profile.json)pidfd_getfdallows duplicating file descriptors from other processes. In a hostile agent scenario, this could be used to duplicate Squid's or the init container's socket FDs if the agent can obtain a pidfd for those processes.pidfd_getfdis required by any legitimate tool used in agent workflows. If not, move it toSCMP_ACT_ERRNO. At minimum, document the rationale for allowing it.🟡 Medium
Explicitly block ICMP in agent OUTPUT chain (
containers/agent/setup-iptables.sh)iptables -A OUTPUT -p icmp -j DROPafter the UDP drop rule (~line 430) to prevent ICMP-based covert channels or ping-based network reconnaissance.Block RFC1918 private ranges at host DOCKER-USER level (
src/host-iptables.ts)10.0.0.0/8,192.168.0.0/16,172.16.0.0/12excluding awf-net). Depending on host network topology, containers could reach internal services.172.30.0.0/24) unless--enable-host-accessis specified.Review the ~1-second token exposure window (
containers/agent/entrypoint.sh, lines 828–837)unset_sensitive_tokensto allow the one-shot-token library to cache credentials. If the agent launches sub-processes quickly, those sub-processes could read/proc/1/environbefore unsetting.getenv()calls for all listed tokens and verify it handles dynamic linking edge cases (e.g., statically linked binaries that bypass LD_PRELOAD).Harden
/tmp/awf-libwrite window for one-shot-token library (containers/agent/entrypoint.sh, lines 431-435)/host/tmp/awf-lib/one-shot-token.sowhich is on the host/tmpbind-mount (world-writable). A fast-starting agent process could potentially replace the.sobefore LD_PRELOAD is applied./var/awf-lib/directory (not bind-mounted from host) for the one-shot-token library in chroot mode, or set strict file permissions (0444 + owned by root) immediately after copy.🟢 Low
Document
kcmpandprocess_madviseallowances — add comments inseccomp-profile.jsonexplaining why these are allowed.Consider
--read-onlyfilesystem for API proxy container — it drops all capabilities but its root filesystem is still writable.Squid ICMP pinger is disabled (
squid-config.ts:589) — already correct. No action needed.📈 Security Metrics
Review conducted: 2026-04-18 by GitHub Copilot CLI security review workflow. Evidence gathered from static code analysis of
src/host-iptables.ts,src/squid-config.ts,src/domain-patterns.ts,src/dlp.ts,containers/agent/setup-iptables.sh,containers/agent/entrypoint.sh,containers/api-proxy/server.js, andcontainers/agent/seccomp-profile.json. No runtime testing was performed in this review.Beta Was this translation helpful? Give feedback.
All reactions