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
Overall security posture: STRONG — Defense-in-depth is well-implemented across network, container, and input validation layers. Zero high/critical npm vulnerabilities. Key design decisions (capability drops, seccomp, Squid injection guards, IPv6 fallback) are correctly implemented. Three identified areas warrant attention: the acknowledged Docker-in-Docker firewall bypass, the intentional single-argument shell pass-through, and the Squid log format's lack of field escaping.
Metric
Value
Files analyzed
8 core security files
npm vulns (high/critical)
0
Attack surfaces identified
6
Critical findings
0
High findings
1
Medium findings
3
Low findings
3
🔍 Findings from Firewall Escape Test
The pre-fetched escape test summary (/tmp/gh-aw/escape-test-summary.txt) contained GitHub Actions workflow run metadata from the "Secret Digger (Copilot)" workflow (run 24273493151). Key observations:
GH_AW_AGENT_CONCLUSION: success
GH_AW_SECRET_VERIFICATION_RESULT: success
Output: Agent succeeded with only noop outputs — meaning the secret-digger agent found no secrets to report and took no action
GH_AW_LOCKDOWN_CHECK_FAILED: false — no lockdown bypass detected
Interpretation: The most recent secret-digger run found no exposed secrets or credential leakage. This corroborates the API proxy sidecar design where real keys never reach the agent environment.
src/host-iptables.ts: Dedicated FW_WRAPPER chain in DOCKER-USER — survives Docker daemon restart (DOCKER-USER is persistent). Chain is flushed and recreated on each run, preventing stale rule accumulation.
Squid gets unrestricted egress (-s squidIp -j ACCEPT); all other container traffic is subject to domain ACL.
IPv6 fallback: if ip6tables is unavailable, sysctl net.ipv6.conf.all.disable_ipv6=1 is set to prevent unfiltered IPv6 bypass (src/host-iptables.ts lines ~148–160).
DNS locked to configured upstream servers only (default: 8.8.8.8, 8.8.4.4) — prevents DNS-based exfiltration.
iptables LOG rules in containers/agent/setup-iptables.sh with [FW_BLOCKED_UDP] / [FW_BLOCKED_OTHER] prefixes provide L3/L4 visibility.
Weaknesses:
src/docker-manager.ts:1314: Explicit warning logged — "Docker-in-Docker enabled: agent can run docker commands (firewall bypass possible)". When DinD is enabled the agent can launch containers on arbitrary networks, completely bypassing Squid's domain ACL. This is a known, acknowledged limitation but has no compensating runtime control beyond the warning.
DNAT of port 80/443 to Squid handles proxy-unaware HTTP/HTTPS tools; however, tools speaking raw TCP/UDP on non-standard ports are only blocked by the broad DROP rule (no application-layer inspection).
Agent container: cap_add: ['SYS_CHROOT', 'SYS_ADMIN'] for chroot/mount, but both are dropped via capsh before user code runs (src/docker-manager.ts lines 1534–1537, entrypoint.sh).
NET_ADMIN is never granted to the agent; only the short-lived awf-iptables-init init container receives it (src/docker-manager.ts:1693), and that container exits before user code starts.
Seccomp profile (containers/agent/seccomp-profile.json) uses defaultAction: SCMP_ACT_ERRNO — deny-by-default with an explicit allowlist of ~300 syscalls.
UID/GID=0 explicitly rejected in entrypoint.sh lines 26–35: if [ "$HOST_UID" -eq 0 ] → error and exit.
System UID/GIDs (0-999) rejected in src/docker-manager.ts:124.
Selective bind mounts: only whitelisted $HOME subdirectories, no /etc/shadow, no blanket host FS.
Weaknesses:
SYS_ADMIN is granted (and only later dropped) — a race window exists if the entrypoint crashes before capsh --drop. The window is small and inside the container, but worth noting.
No --read-only on the root filesystem; the agent's container rootfs is writable (mitigated by chroot and capability drops, but defense-in-depth would prefer read-only + explicit tmpfs for writable paths).
functionassertSafeForSquidConfig(value: string): void{if(SQUID_DANGEROUS_CHARS.test(value)){thrownewError(`SECURITY: Domain or pattern contains characters unsafe for Squid config...`);}}
Strengths:
All domain inputs pass through assertSafeForSquidConfig before interpolation into squid.conf — guards against newline injection, comment injection (#), and whitespace token-splitting.
URL regex patterns use the same guard (src/squid-config.ts:211).
Port sanitization strips non-numeric/dash chars: port.replace(/[^0-9-]/g, '') (src/squid-config.ts:567).
Redundant subdomain elimination prevents ACL bloat that could be used for DoS.
Weaknesses:
src/squid-config.ts:601 comment acknowledges: "Squid logformat does not JSON-escape strings, so fields like User-Agent may contain raw characters" — log injection is possible via a crafted User-Agent header. This is a log integrity issue rather than a runtime security control bypass, but could mislead log analysis tooling.
Key design decision (src/cli.ts:1595–1604):
Single-argument mode passes the command string as-is to the container shell. This is intentional — shell variables like $HOME must expand inside the container, not on the host.
Strengths:
Multi-argument mode: each arg is passed through escapeShellArg → joinShellArgs before container execution, preventing host-side injection.
isValidPortSpec() in src/host-iptables.ts validates port specs with strict regex before use in iptables commands.
Weaknesses (Medium):
Single-argument pass-through means a user running awf -- "$(malicious_cmd)" will expand malicious_cmd on the host (their own shell) before AWF receives it — this is expected POSIX behavior, not a vulnerability in AWF itself, but documentation could be clearer about this distinction for operators.
--allow-domains-file reads a user-supplied file path without path traversal checks. A symlink pointing to /etc/passwd would be parsed as domains (harmless but unexpected). File reading uses fs.readFileSync with no realpath resolution.
⚠️ Threat Model (STRIDE)
Category
Threat
Location
Likelihood
Impact
Severity
Spoofing
Agent spoofs Squid IP to bypass FW_WRAPPER
Container network (172.30.0.0/24)
Low — fixed IPs assigned by Docker
High
Medium
Spoofing
DNS response spoofing via rogue resolver
Container → DNS path
Low — DNS locked to 8.8.8.8/8.8.4.4
Medium
Low
Tampering
Squid config injection via crafted domain in --allow-domains
src/squid-config.ts
Low — assertSafeForSquidConfig guards
Critical
Low
Tampering
Log injection via crafted User-Agent in HTTP requests
Squid access.log
Medium — no escaping in logformat
Low
Low
Tampering
iptables rule removal by agent with NET_ADMIN
Container
Very Low — NET_ADMIN not granted to agent
Critical
Low
Repudiation
Agent denies network access — Squid logs not preserved post-run
src/cli.ts cleanup
Medium — logs deleted unless --keep-containers
Medium
Medium
Information Disclosure
API keys visible in env if --enable-api-proxy not used
Agent env vars
Medium — operator must opt in to proxy
High
Medium
Information Disclosure
/etc files (passwd, group, certs) readable in chroot
Container bind mounts
Low — shadow not mounted
Low
Low
Denial of Service
ACL table explosion via large --allow-domains list
src/squid-config.ts
Low — no count limit enforced
Medium
Medium
Denial of Service
Race on awf-net network creation (pool overlaps)
src/host-iptables.ts
Medium — known CI issue, pre-test cleanup added
Medium
Medium
Elevation of Privilege
DinD escape: agent launches containers on arbitrary networks
src/docker-manager.ts:1314
Medium when DinD enabled
Critical
High
Elevation of Privilege
SYS_ADMIN race before capsh drop
containers/agent/entrypoint.sh
Very Low — millisecond window inside container
High
Low
🎯 Attack Surface Map
Surface
Entry Point
Current Protections
Risk
CLI input
src/cli.ts:41 (parseDomains)
Char validation, assertSafeForSquidConfig
Low
Squid config
src/squid-config.ts:102
assertSafeForSquidConfig, port sanitization
Low
Container network
172.30.0.0/24, Squid port 3128
FW_WRAPPER iptables chain, domain ACL
Low
Agent container
containers/agent/entrypoint.sh
Capability drop, seccomp, UID validation
Low
DinD path
src/docker-manager.ts:1314
Warning log only
High
Log pipeline
Squid access.log
No field escaping in logformat
Medium
DNS path
Agent → 127.0.0.11 → upstream
Upstream DNS allowlist in iptables
Low
API proxy sidecar
172.30.0.30:10001 (optional)
No auth required from agent (by design), routed through Squid
// src/squid-config.ts:102functionassertSafeForSquidConfig(value: string): string{if(SQUID_DANGEROUS_CHARS.test(value)){thrownewError(`SECURITY: Domain or pattern contains characters unsafe for Squid config and cannot be `+`interpolated into squid.conf: \$\{JSON.stringify(value)}`);}returnvalue;}
Command: Capability configuration
// src/docker-manager.ts:1531-1554 (agent container)// NET_ADMIN is NOT granted to the agent container.// SYS_CHROOT is required for chroot operations.// SYS_ADMIN is required to mount procfs.// Security: SYS_CHROOT and SYS_ADMIN are dropped before running user commandscap_add: ['SYS_CHROOT','SYS_ADMIN'],cap_drop: ['ALL'],// except SYS_CHROOT and SYS_ADMIN// iptables-init container (src/docker-manager.ts:1693)cap_add: ['NET_ADMIN','NET_RAW'],cap_drop: ['ALL'],
Command: IPv6 fallback mechanism
// src/host-iptables.tsconstip6tablesAvailable=awaitisIp6tablesAvailable();if(!ip6tablesAvailable){logger.warn('ip6tables not available, disabling IPv6 via sysctl...');awaitdisableIpv6ViaSysctl();// sets net.ipv6.conf.all.disable_ipv6=1}
Command: DinD bypass warning
// src/docker-manager.ts:1314logger.warn('Docker-in-Docker enabled: agent can run docker commands (firewall bypass possible)');
Command: Escape test result (Secret Digger run 24273493151)
GH_AW_AGENT_CONCLUSION: success
GH_AW_SECRET_VERIFICATION_RESULT: success
GH_AW_LOCKDOWN_CHECK_FAILED: false
GH_AW_INFERENCE_ACCESS_ERROR: false
Agent succeeded with only noop outputs - this is not a failure
No secrets found; no escape detected.
✅ Recommendations
🔴 High — Should fix soon
DinD firewall bypass (src/docker-manager.ts:1314)
Issue: When DinD is enabled, the agent can create Docker networks and launch containers that bypass AWF's iptables rules entirely.
Recommendation: When DinD is enabled, either (a) mount a Docker socket shim that rejects network create / run --network commands, or (b) add a host-level iptables rule blocking egress from the DinD subnet on all non-Squid paths. At minimum, add a prominent warning in docs that DinD reduces the firewall guarantee to advisory-only.
🟡 Medium — Plan to address
Log field injection via User-Agent (src/squid-config.ts:601)
Issue: Squid's logformat does not escape fields; a crafted User-Agent: foo\nTCP_TUNNEL 200 allowed.example.com could inject fake allow entries into access logs.
Recommendation: Post-process logs through a field-escaping pipeline, or switch the logformat to use %{User-Agent}>h with a sanitization layer in src/logs/log-aggregator.ts that strips/escapes non-printable and newline characters before analysis.
Missing --allow-domains count limit (src/cli.ts)
Issue: No upper bound on number of domains; a very large list could cause Squid startup latency or ACL table pressure.
Recommendation: Add a configurable limit (e.g., 1000 domains max) with a clear error message.
Logs deleted by default (cleanup in src/cli.ts)
Issue: Squid access logs are deleted post-run unless --keep-containers is used, making forensic analysis impossible without prior opt-in.
Recommendation: Consider always writing a summary log file to a persistent path (e.g., ~/.awf/logs/) regardless of --keep-containers, with a --no-log opt-out for users who explicitly don't want it.
🔵 Low — Nice to have
Add --read-only to agent container rootfs (src/docker-manager.ts)
Add read_only: true to the agent service with explicit tmpfs mounts for /tmp, /run, etc. Reduces blast radius if a capability escape occurs.
Resolve symlinks for --allow-domains-file (src/cli.ts:54)
Call fs.realpathSync before reading the file to prevent unexpected behavior with symlinks, even though current behavior is harmless.
Add a user-facing note clarifying that in single-argument mode, shell expansion (e.g., $(cmd), $VAR) occurs in the user's host shell before AWF receives the string. This is expected POSIX behavior but is a common source of confusion.
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
Overall security posture: STRONG — Defense-in-depth is well-implemented across network, container, and input validation layers. Zero high/critical npm vulnerabilities. Key design decisions (capability drops, seccomp, Squid injection guards, IPv6 fallback) are correctly implemented. Three identified areas warrant attention: the acknowledged Docker-in-Docker firewall bypass, the intentional single-argument shell pass-through, and the Squid log format's lack of field escaping.
🔍 Findings from Firewall Escape Test
The pre-fetched escape test summary (
/tmp/gh-aw/escape-test-summary.txt) contained GitHub Actions workflow run metadata from the "Secret Digger (Copilot)" workflow (run24273493151). Key observations:GH_AW_AGENT_CONCLUSION: successGH_AW_SECRET_VERIFICATION_RESULT: successAgent succeeded with only noop outputs— meaning the secret-digger agent found no secrets to report and took no actionGH_AW_LOCKDOWN_CHECK_FAILED: false— no lockdown bypass detectedGH_AW_INFERENCE_ACCESS_ERROR: false— inference credentials properly controlledInterpretation: The most recent secret-digger run found no exposed secrets or credential leakage. This corroborates the API proxy sidecar design where real keys never reach the agent environment.
🛡️ Architecture Security Analysis
Network Security Assessment
Evidence gathered:
grep -n "ACCEPT\|DROP\|RETURN\|squidIp\|dnsServer" src/host-iptables.tsStrengths:
src/host-iptables.ts: DedicatedFW_WRAPPERchain inDOCKER-USER— survives Docker daemon restart (DOCKER-USER is persistent). Chain is flushed and recreated on each run, preventing stale rule accumulation.-s squidIp -j ACCEPT); all other container traffic is subject to domain ACL.ip6tablesis unavailable,sysctl net.ipv6.conf.all.disable_ipv6=1is set to prevent unfiltered IPv6 bypass (src/host-iptables.tslines ~148–160).8.8.8.8, 8.8.4.4) — prevents DNS-based exfiltration.containers/agent/setup-iptables.shwith[FW_BLOCKED_UDP]/[FW_BLOCKED_OTHER]prefixes provide L3/L4 visibility.Weaknesses:
src/docker-manager.ts:1314: Explicit warning logged — "Docker-in-Docker enabled: agent can run docker commands (firewall bypass possible)". When DinD is enabled the agent can launch containers on arbitrary networks, completely bypassing Squid's domain ACL. This is a known, acknowledged limitation but has no compensating runtime control beyond the warning.Container Security Assessment
Evidence gathered:
Strengths:
cap_add: ['SYS_CHROOT', 'SYS_ADMIN']for chroot/mount, but both are dropped viacapshbefore user code runs (src/docker-manager.tslines 1534–1537,entrypoint.sh).NET_ADMINis never granted to the agent; only the short-livedawf-iptables-initinit container receives it (src/docker-manager.ts:1693), and that container exits before user code starts.containers/agent/seccomp-profile.json) usesdefaultAction: SCMP_ACT_ERRNO— deny-by-default with an explicit allowlist of ~300 syscalls.entrypoint.shlines 26–35:if [ "$HOST_UID" -eq 0 ]→ error and exit.src/docker-manager.ts:124.$HOMEsubdirectories, no/etc/shadow, no blanket host FS.Weaknesses:
SYS_ADMINis granted (and only later dropped) — a race window exists if the entrypoint crashes beforecapsh --drop. The window is small and inside the container, but worth noting.--read-onlyon the root filesystem; the agent's container rootfs is writable (mitigated by chroot and capability drops, but defense-in-depth would prefer read-only + explicit tmpfs for writable paths).Domain Validation Assessment
Evidence gathered:
grep -n "assertSafeForSquidConfig\|SQUID_DANGEROUS_CHARS" src/squid-config.ts src/domain-patterns.tsKey implementation (
src/domain-patterns.ts:168,src/squid-config.ts:102):Strengths:
assertSafeForSquidConfigbefore interpolation intosquid.conf— guards against newline injection, comment injection (#), and whitespace token-splitting.src/squid-config.ts:211).port.replace(/[^0-9-]/g, '')(src/squid-config.ts:567).Weaknesses:
src/squid-config.ts:601comment acknowledges: "Squid logformat does not JSON-escape strings, so fields like User-Agent may contain raw characters" — log injection is possible via a crafted User-Agent header. This is a log integrity issue rather than a runtime security control bypass, but could mislead log analysis tooling.Input Validation Assessment
Evidence gathered:
grep -n "SINGLE ARGUMENT\|escapeShellArg\|joinShellArgs" src/cli.tsKey design decision (
src/cli.ts:1595–1604):Single-argument mode passes the command string as-is to the container shell. This is intentional — shell variables like
$HOMEmust expand inside the container, not on the host.Strengths:
escapeShellArg→joinShellArgsbefore container execution, preventing host-side injection.isValidPortSpec()insrc/host-iptables.tsvalidates port specs with strict regex before use in iptables commands.Weaknesses (Medium):
awf -- "$(malicious_cmd)"will expandmalicious_cmdon the host (their own shell) before AWF receives it — this is expected POSIX behavior, not a vulnerability in AWF itself, but documentation could be clearer about this distinction for operators.--allow-domains-filereads a user-supplied file path without path traversal checks. A symlink pointing to/etc/passwdwould be parsed as domains (harmless but unexpected). File reading usesfs.readFileSyncwith no realpath resolution.--allow-domainssrc/squid-config.tsassertSafeForSquidConfigguardssrc/cli.tscleanup--keep-containers--enable-api-proxynot used/etcfiles (passwd, group, certs) readable in chroot--allow-domainslistsrc/squid-config.tsawf-netnetwork creation (pool overlaps)src/host-iptables.tssrc/docker-manager.ts:1314containers/agent/entrypoint.sh🎯 Attack Surface Map
src/cli.ts:41(parseDomains)assertSafeForSquidConfigsrc/squid-config.ts:102assertSafeForSquidConfig, port sanitization172.30.0.0/24, Squid port 3128containers/agent/entrypoint.shsrc/docker-manager.ts:1314access.log127.0.0.11→ upstream172.30.0.30:10001(optional)📋 Evidence Collection
Command: Check npm audit
Command: Squid injection guard implementation
Command: Capability configuration
Command: IPv6 fallback mechanism
Command: DinD bypass warning
Command: Escape test result (Secret Digger run 24273493151)
No secrets found; no escape detected.
✅ Recommendations
🔴 High — Should fix soon
src/docker-manager.ts:1314)network create/run --networkcommands, or (b) add a host-level iptables rule blocking egress from the DinD subnet on all non-Squid paths. At minimum, add a prominent warning in docs that DinD reduces the firewall guarantee to advisory-only.🟡 Medium — Plan to address
Log field injection via User-Agent (
src/squid-config.ts:601)User-Agent: foo\nTCP_TUNNEL 200 allowed.example.comcould inject fake allow entries into access logs.%{User-Agent}>hwith a sanitization layer insrc/logs/log-aggregator.tsthat strips/escapes non-printable and newline characters before analysis.Missing
--allow-domainscount limit (src/cli.ts)Logs deleted by default (cleanup in
src/cli.ts)--keep-containersis used, making forensic analysis impossible without prior opt-in.~/.awf/logs/) regardless of--keep-containers, with a--no-logopt-out for users who explicitly don't want it.🔵 Low — Nice to have
Add
--read-onlyto agent container rootfs (src/docker-manager.ts)read_only: trueto the agent service with explicittmpfsmounts for/tmp,/run, etc. Reduces blast radius if a capability escape occurs.Resolve symlinks for
--allow-domains-file(src/cli.ts:54)fs.realpathSyncbefore reading the file to prevent unexpected behavior with symlinks, even though current behavior is harmless.Document single-argument shell expansion scope (
src/cli.ts:1595)$(cmd),$VAR) occurs in the user's host shell before AWF receives the string. This is expected POSIX behavior but is a common source of confusion.📈 Security Metrics
Review conducted: 2026-04-22 | Workflow run: 24779915187
Beta Was this translation helpful? Give feedback.
All reactions