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
This review covers a deep, evidence-based security analysis of the gh-aw-firewall codebase as of 2026-03-09. The overall security posture is strong: credential isolation, capability dropping, DNS restriction, and network ACL ordering are all implemented correctly. Two medium-severity configuration issues were identified — an allow-all seccomp default and a disabled AppArmor profile — along with one low-severity Squid DNS inconsistency and one note-level gap in custom mount path validation. No critical vulnerabilities were found, and no npm dependency vulnerabilities exist.
Severity
Count
Status
Critical
0
✅ Clear
High
0
✅ Clear
Medium
2
⚠️ See below
Low
1
ℹ️ See below
Info/Note
2
📋 See below
🔍 Findings from Firewall Escape Test Agent
The firewall-escape-test workflow is not listed in the current workflow registry (agenticworkflows-status showed no such workflow). The closest security-adjacent workflows are security-review (daily) and security-guard (per-PR). No historical escape-test run data was available for cross-reference. This review is based purely on static codebase analysis.
# Rule ordering (correct precedence):
1. Allow loopback traffic (RETURN)
2. Allow self-directed traffic (agent IP bypass)
3. Allow DNS to trusted servers only (AWF_DNS_SERVERS)
4. Allow Docker embedded DNS (127.0.0.11)
5. Allow Squid proxy itself (prevent redirect loop)
6. Block dangerous ports (22/23/25/110/143/445/1433/3306/3389/5432/6379/27017)
7. DNAT HTTP(80) and HTTPS(443) to Squid
8. Default DROP for all other TCP
```
**Evidence — host-level DOCKER-USER chain (`src/host-iptables.ts:283-537`):**
- Squid gets explicit ACCEPT before firewall chain (line 289)
- ESTABLISHED/RELATED conntrack allows return traffic (line 298)
- UDP traffic blocked at host level with LOG prefix `[FW_BLOCKED_UDP]`
- IPv6 has its own dedicated chain (`FW_WRAPPER_V6`) with full mirroring of IPv4 rules
**DNS exfiltration prevention:** `setup-iptables.sh` reads `AWF_DNS_SERVERS` and builds per-server ACCEPT rules for port 53 UDP/TCP, then `DROP`s all other UDP — correctly preventing DNS tunneling to arbitrary resolvers.
**IPv6 handling:** If `ip6tables` is unavailable, both `setup-iptables.sh` (line ~30) and `host-iptables.ts:91-94` disable IPv6 entirely via `sysctl net.ipv6.conf.all.disable_ipv6=1`. This prevents IPv6 from becoming an unfiltered bypass path.
---
### Container Security Assessment
#### ⚠️ MEDIUM-1: Seccomp Profile Uses Default-Allow (SCMP_ACT_ALLOW)
**Evidence:**
```
$ python3 -c "import json; d=json.load(open('containers/agent/seccomp-profile.json')); print('defaultAction:', d['defaultAction'])"
defaultAction: SCMP_ACT_ALLOW
The seccomp profile (containers/agent/seccomp-profile.json:2) specifies "defaultAction": "SCMP_ACT_ALLOW". The profile only explicitly denies 28 syscalls:
This allow-all-except approach leaves many potentially dangerous syscalls accessible, including unshare (namespace manipulation), clone3 with new namespace flags, and mount (if capability allows). The Docker documentation recommends a deny-by-default seccomp profile for untrusted workloads. The gap is partially mitigated by capability restrictions, but the seccomp profile represents an independent security layer that is currently weak.
Mitigation: Invert the profile to use "defaultAction": "SCMP_ACT_ERRNO" and add an explicit allowlist of required syscalls, following the Docker default seccomp profile as a baseline.
⚠️ MEDIUM-2: AppArmor Set to Unconfined
Evidence (src/docker-manager.ts:883-885):
security_opt: ['no-new-privileges:true',`seccomp=\$\{config.workDir}/seccomp-profile.json`,'apparmor:unconfined',// ← MAC layer disabled],
The agent container runs with apparmor:unconfined, removing the Mandatory Access Control (MAC) layer. The code comment explains this is necessary to allow mounting procfs at /host/proc (Docker's default AppArmor profile blocks the mount call). While capability restrictions prevent user code from mounting anything (SYS_ADMIN is dropped before user command), the MAC absence represents a defense-in-depth gap during the setup phase (before capsh --drop).
Mitigation: Write a custom AppArmor profile that permits only the specific mount operations needed during initialization, then restricts access during user command execution. Alternatively, document this explicitly as an accepted risk given the capability drop.
✅ Capability Dropping — Correctly Implemented
Evidence (src/docker-manager.ts:870-879):
cap_add: ['NET_ADMIN','SYS_CHROOT','SYS_ADMIN'],cap_drop: ['NET_RAW',// Prevents raw socket creation (iptables bypass attempts)'SYS_PTRACE',// Prevents process inspection/debugging
...
],
Evidence (containers/agent/entrypoint.sh):
Capabilities are dropped via capsh --drop=cap_net_admin,... before the user command executes. This is verified to occur after iptables setup and before any user-controlled code runs. NET_RAW is dropped to prevent raw socket bypass of iptables.
Credentials are masked at both $HOME and /host$HOME paths. The Docker socket is hidden at /host/var/run/docker.sock and /host/run/docker.sock (line 681-685). This correctly mitigates prompt injection attacks targeting credential exfiltration.
Domain Validation Assessment ✅ Strong
Evidence (src/domain-patterns.ts:126-140):
// Blocks overly broad patterns:if(trimmed==='*')→ Error: "Pattern '*' matches all domains"if(trimmed==='*.*')→ Error: "Pattern '*.*' is too broad"if(/^[*.]+$/.test(trimmed))→ Error: "Pattern is too broad"if(wildcardSegments>1&&wildcardSegments>=totalSegments-1)→ Error: "Too many wildcard segments"
ReDoS protection (src/domain-patterns.ts:86-95):
constDOMAIN_CHAR_PATTERN='[a-zA-Z0-9.-]*';// character class, not .* — safe from catastrophic backtracking
And length validation before regex execution:
constMAX_DOMAIN_LENGTH=512;if(domainEntry.domain.length>MAX_DOMAIN_LENGTH)returnfalse;```**Squid blocklist precedence (`src/squid-config.ts:380-396`):**```
# Denyrequeststoblockeddomains(blocklisttakesprecedence)http_accessdenyblocked_domainshttp_accessdenyblocked_domains_regex[thenallowrules]```---### Input Validation Assessment#### ℹ️ INFO-1: Squid DNS Hardcoded to Google (Inconsistency)**Evidence (`src/squid-config.ts:551`):**```dns_nameservers8.8.8.88.8.4.4```This line is always written verbatim in the generated `squid.conf`, regardless of the `--dns-servers` flag. The `--dns-servers` flag correctly configures iptables rules and `/etc/resolv.conf` in the agent container, but Squid itself always resolves upstream domains via Google DNS. This creates a DNS resolution inconsistency between the agent process and Squid, and could potentially allow Squid to reach Google DNS even if the user specified different servers and restricted iptables accordingly (Squid container has unrestricted outbound access via the Squid ACCEPT rule in DOCKER-USER chain).**Recommendation:** Pass the configured DNS servers into `generateSquidConfig()` and write them to the `dns_nameservers` directive. This is a correctness issue rather than a firewall bypass (Squid has full outbound access by design), but worth fixing for consistency.---#### ℹ️ INFO-2: Custom Volume Mount Has No Sensitive Path Blocklist**Evidence (`src/cli.ts:576-660`, `parseVolumeMounts` function):**The function validates that paths are absolute, exist, and have valid modes — but does not block sensitive host paths like `/etc/shadow`, `/proc/sysrq-trigger`, or `/sys/kernel`. Since this flag requires root to use (awf requires sudo), the attack surface is limited to privileged users, making this a note rather than a vulnerability.**Recommendation:** Document in CLI help text that `--mount` should only be used with trusted paths, and consider warning if a sensitive system path is specified.---## ⚠️ Threat Model (STRIDE)| Threat | Category | Evidence | Likelihood | Impact | Status ||--------|----------|----------|------------|--------|--------|| DNS tunneling to arbitrary resolver | Info Disclosure | `setup-iptables.sh` blocks all UDP except trusted servers | Low | Medium | ✅ Mitigated || IPv6 bypass of iptables | Spoofing/Bypass | `ip6tables` unavailable → sysctl disable | Low | High | ✅ Mitigated || Raw socket bypass of Squid | Bypass | `NET_RAW` dropped, `SYS_PTRACE` dropped | Low | High | ✅ Mitigated || Prompt injection → credential theft | Info Disclosure | All credential files masked with /dev/null | Low | High | ✅ Mitigated || Docker-in-Docker firewall escape | Elevation of Privilege | docker.sock hidden at /host path | Low | Critical | ✅ Mitigated || Privilege escalation via setuid | Elevation of Privilege | `no-new-privileges:true` | Low | High | ✅ Mitigated || Container escape via dangerous syscall | Elevation of Privilege | Seccomp uses SCMP_ACT_ALLOW default | **Medium** | High | ⚠️ Partial || MAC bypass during init phase | Elevation of Privilege | AppArmor unconfined | **Medium** | Medium | ⚠️ Partial || Domain allowlist bypass via Squid DNS inconsistency | Spoofing | Squid hardcoded to 8.8.8.8 | Low | Low | ℹ️ Info || Overly broad domain pattern (e.g. `*`) | Bypass | Validation blocks `*`, `*.*`, many wildcards | Low | High | ✅ Mitigated || Resource exhaustion (DoS) | Denial of Service | `mem_limit: 4g`, `pids_limit: 1000` | Low | Medium | ✅ Mitigated |---## 🎯 Attack Surface Map| Entry Point | File:Line | Function | Current Protections | Risk ||-------------|-----------|----------|---------------------|------|| `--allow-domains` CLI input | `src/domain-patterns.ts:126` | `validateDomainOrPattern` | Blocklist of overly broad patterns, double-dot check | **Low** || `--mount` CLI input | `src/cli.ts:576` | `parseVolumeMounts` | Absolute path check, existence check, mode validation | **Low** || `agentCommand` bash execution | `src/docker-manager.ts:896` | Docker compose command field | `$` → `$$` escaping for compose interpolation; shell expansion is intentional | **Low** || Squid ACL domain matching | `src/squid-config.ts:385` | `generateSquidConfig` | Blocklist-before-allowlist ordering | **Low** || DNS resolution (container) | `containers/agent/setup-iptables.sh:85` | iptables OUTPUT | Trusted-DNS-only whitelist, Docker DNS always allowed | **Low** || DNS resolution (Squid) | `src/squid-config.ts:551` | Generated `squid.conf` | Hardcoded to 8.8.8.8 (Squid has full outbound access) | **Info** || Container syscall surface | `containers/agent/seccomp-profile.json:2` | Seccomp filter | Only 28 syscalls covered, default ALLOW | **Medium** || Container MAC (AppArmor) | `src/docker-manager.ts:884` | Docker security_opt | Disabled (`unconfined`) | **Medium** || Custom volume mounts | `src/docker-manager.ts:744` | Volume mount injection | No path blocklist for sensitive system paths | **Low** || IPv6 network | `src/host-iptables.ts:370` | `isIp6tablesAvailable` | Disable IPv6 via sysctl if ip6tables unavailable | **Low** || UDP (non-DNS) traffic | `src/host-iptables.ts:526-537` | DOCKER-USER chain | UDP blocked at host chain | **Low** |---## 📋 Evidence Collection<details><summary>Seccomp Profile Default Action</summary>```$python3-c "import json; d=json.load(open('containers/agent/seccomp-profile.json'));
print('defaultAction:',d['defaultAction'])print('Total syscalls covered:',sum(len(s['names'])forsind.get('syscalls',[])))print('DENIED:',[s['names']forsind['syscalls']ifs['action']=='SCMP_ACT_ERRNO'])"
defaultAction: SCMP_ACT_ALLOWTotalsyscallscovered: 28DENIED:
['ptrace','process_vm_readv','process_vm_writev']['kexec_load','kexec_file_load','reboot','init_module','finit_module','delete_module','acct','swapon','swapoff','pivot_root','syslog','add_key','request_key','keyctl','uselib','personality','ustat','sysfs','vhangup','get_kernel_syms','query_module','create_module','nfsservctl']['umount','umount2']```</details><details><summary>AppArmor Unconfined Setting</summary>```$grep-n"apparmor"src/docker-manager.ts884: 'apparmor:unconfined',```</details><details><summary>Hardcoded Squid DNS</summary>```$grep-n"dns_nameservers"src/squid-config.ts551:dns_nameservers8.8.8.88.8.4.4```</details><details><summary>npm audit result</summary>```$npmaudit--json|python3-c"import json,sys; d=json.load(sys.stdin); print(d['metadata']['vulnerabilities'])"{'info': 0,'low': 0,'moderate': 0,'high': 0,'critical': 0,'total': 0}```</details><details><summary>Host iptables UDP blocking</summary>```
# src/host-iptables.ts:526-537(IPv4)
# 7.BlockallotherUDPtrafficiptablesREJECT--reject-withicmp-port-unreachable(withLOGprefix[FW_BLOCKED_UDP])
# src/host-iptables.ts:445-455(IPv6)
# 6.BlockallotherIPv6UDPtrafficip6tablesREJECT--reject-withicmp6-port-unreachable(withLOGprefix[FW_BLOCKED_UDP6])
✅ Recommendations
🔴 Medium: Harden Seccomp Profile to Default-Deny
File:containers/agent/seccomp-profile.json:2
Change "defaultAction": "SCMP_ACT_ALLOW" to "defaultAction": "SCMP_ACT_ERRNO" and add an explicit allowlist of all required syscalls. Use the Docker default seccomp allowlist as a starting point and prune syscalls not needed by the agent container. This provides the most defense against container escape via kernel exploitation.
The apparmor:unconfined setting removes the entire MAC layer. Either:
Write a custom AppArmor profile that allows only specific mounts during initialization, or
Restructure the initialization to avoid needing mount at all during the capability-privileged phase
At minimum, add a warning in documentation and CLI output when this mode is active.
🟡 Low: Propagate --dns-servers to Squid's dns_nameservers
File:src/squid-config.ts:551
Pass the configured DNS servers through SquidConfig and write them to dns_nameservers. This ensures Squid resolves domains using the same trusted servers as the rest of the container, rather than always using Google DNS regardless of user configuration.
🟢 Info: Document Custom Mount Path Restrictions
File:src/cli.ts:570-660 (parseVolumeMounts)
Add documentation noting that --mount is a privileged operation. Consider adding a warning when sensitive system directories (e.g., /proc, /sys, /dev, /etc) are included in mount paths.
🟢 Info: Add Escape Test Workflow
There is no firewall-escape-test workflow in the current workflow registry. Adding an automated test workflow that attempts known escape techniques (DNS tunneling, raw socket creation, IPv6 bypass, ICMP tunneling) would provide continuous confidence in firewall effectiveness. This complements static analysis.
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
This review covers a deep, evidence-based security analysis of the
gh-aw-firewallcodebase as of 2026-03-09. The overall security posture is strong: credential isolation, capability dropping, DNS restriction, and network ACL ordering are all implemented correctly. Two medium-severity configuration issues were identified — an allow-all seccomp default and a disabled AppArmor profile — along with one low-severity Squid DNS inconsistency and one note-level gap in custom mount path validation. No critical vulnerabilities were found, and no npm dependency vulnerabilities exist.🔍 Findings from Firewall Escape Test Agent
The
firewall-escape-testworkflow is not listed in the current workflow registry (agenticworkflows-statusshowed no such workflow). The closest security-adjacent workflows aresecurity-review(daily) andsecurity-guard(per-PR). No historical escape-test run data was available for cross-reference. This review is based purely on static codebase analysis.🛡️ Architecture Security Analysis
Network Security Assessment ✅ Strong
Evidence — iptables rule ordering (
containers/agent/setup-iptables.sh):The seccomp profile (
containers/agent/seccomp-profile.json:2) specifies"defaultAction": "SCMP_ACT_ALLOW". The profile only explicitly denies 28 syscalls:ptrace,process_vm_readv,process_vm_writev(anti-debugging)kexec_load,kexec_file_load,reboot,init_module,finit_module,delete_module(kernel/boot)pivot_root,syslog,add_key,request_key,keyctl(system-level operations)umount,umount2(unmounting)This allow-all-except approach leaves many potentially dangerous syscalls accessible, including
unshare(namespace manipulation),clone3with new namespace flags, andmount(if capability allows). The Docker documentation recommends a deny-by-default seccomp profile for untrusted workloads. The gap is partially mitigated by capability restrictions, but the seccomp profile represents an independent security layer that is currently weak.Mitigation: Invert the profile to use
"defaultAction": "SCMP_ACT_ERRNO"and add an explicit allowlist of required syscalls, following the Docker default seccomp profile as a baseline.Evidence (
src/docker-manager.ts:883-885):The agent container runs with
apparmor:unconfined, removing the Mandatory Access Control (MAC) layer. The code comment explains this is necessary to allow mounting procfs at/host/proc(Docker's default AppArmor profile blocks themountcall). While capability restrictions prevent user code from mounting anything (SYS_ADMIN is dropped before user command), the MAC absence represents a defense-in-depth gap during the setup phase (beforecapsh --drop).Mitigation: Write a custom AppArmor profile that permits only the specific mount operations needed during initialization, then restricts access during user command execution. Alternatively, document this explicitly as an accepted risk given the capability drop.
✅ Capability Dropping — Correctly Implemented
Evidence (
src/docker-manager.ts:870-879):Evidence (
containers/agent/entrypoint.sh):Capabilities are dropped via
capsh --drop=cap_net_admin,...before the user command executes. This is verified to occur after iptables setup and before any user-controlled code runs.NET_RAWis dropped to prevent raw socket bypass of iptables.Evidence (
src/docker-manager.ts:882):✅ Credential Isolation — Correctly Implemented
Evidence (
src/docker-manager.ts:756-784):Credentials are masked at both
$HOMEand/host$HOMEpaths. The Docker socket is hidden at/host/var/run/docker.sockand/host/run/docker.sock(line 681-685). This correctly mitigates prompt injection attacks targeting credential exfiltration.Domain Validation Assessment ✅ Strong
Evidence (
src/domain-patterns.ts:126-140):ReDoS protection (
src/domain-patterns.ts:86-95):And length validation before regex execution:
✅ Recommendations
🔴 Medium: Harden Seccomp Profile to Default-Deny
File:
containers/agent/seccomp-profile.json:2Change
"defaultAction": "SCMP_ACT_ALLOW"to"defaultAction": "SCMP_ACT_ERRNO"and add an explicit allowlist of all required syscalls. Use the Docker default seccomp allowlist as a starting point and prune syscalls not needed by the agent container. This provides the most defense against container escape via kernel exploitation.🔴 Medium: Address AppArmor Unconfined Configuration
File:
src/docker-manager.ts:884The
apparmor:unconfinedsetting removes the entire MAC layer. Either:mountat all during the capability-privileged phaseAt minimum, add a warning in documentation and CLI output when this mode is active.
🟡 Low: Propagate --dns-servers to Squid's dns_nameservers
File:
src/squid-config.ts:551Pass the configured DNS servers through
SquidConfigand write them todns_nameservers. This ensures Squid resolves domains using the same trusted servers as the rest of the container, rather than always using Google DNS regardless of user configuration.🟢 Info: Document Custom Mount Path Restrictions
File:
src/cli.ts:570-660(parseVolumeMounts)Add documentation noting that
--mountis a privileged operation. Consider adding a warning when sensitive system directories (e.g.,/proc,/sys,/dev,/etc) are included in mount paths.🟢 Info: Add Escape Test Workflow
There is no
firewall-escape-testworkflow in the current workflow registry. Adding an automated test workflow that attempts known escape techniques (DNS tunneling, raw socket creation, IPv6 bypass, ICMP tunneling) would provide continuous confidence in firewall effectiveness. This complements static analysis.📈 Security Metrics
Beta Was this translation helpful? Give feedback.
All reactions