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 the AWF (Agentic Workflow Firewall) codebase with evidence gathered by direct source inspection. The firewall's defense-in-depth architecture is generally sound, with multiple overlapping layers. No critical vulnerabilities were found. Four medium-severity findings and five low/informational findings are documented below with verifiable evidence.
Security Posture Overview
Layer
Status
Notes
Domain whitelisting (Squid ACL)
✅ Strong
dstdomain + dstdom_regex, ReDoS-safe wildcards
IP-direct bypass prevention
✅ Strong
dst_ipv4/dst_ipv6 ACLs deny raw IP CONNECTs
Container network isolation
✅ Good
Fixed subnet, DOCKER-USER chain, FW_WRAPPER chain
IPv4 egress filtering (container)
✅ Good
TCP/UDP DROP at OUTPUT filter; DNAT to Squid
Privilege dropping
✅ Good
capsh --drop before user code; UID ≠ 0 enforced
IPv6 egress filtering
⚠️ Gap
Container OUTPUT chain drops IPv4 only
AppArmor
⚠️ Gap
apparmor:unconfined to permit procfs mount
ICMP filtering
⚠️ Gap
Not blocked in OUTPUT filter chain
DLP (credential leak)
⚠️ Opt-in
Not enabled by default
🔍 Findings from Previous Security Workflow Runs
The security-review and secret-digger-* workflows are all compiled and active, but log downloads were unavailable in this run due to the environment not having git-based remote access. Findings below are based entirely on static source analysis.
🛡️ Architecture Security Analysis
Network Security Assessment
Squid ACL rule ordering is correct. Evidence from src/squid-config.ts:541–565:
# Block raw IP CONNECT (no domain name)
http_access deny dst_ipv4
http_access deny dst_ipv6
# [DLP rules, blocked domain rules, allow rules]
# Deny rule (non-allowlisted domains)
http_access deny !allowed_domains !allowed_domains_regex
# Then allow localnet (only reached for traffic that passed domain filter)
http_access allow localnet
http_access allow localhost
http_access deny all
Squid evaluates first-match, so non-allowed domains are denied before the allow localnet rule is reached. ✅
Host-level iptables chain is well-structured.src/host-iptables.ts builds a dedicated FW_WRAPPER chain inserted into DOCKER-USER, scoping rules to the specific bridge interface. Order:
Allow Squid source IP (unrestricted outbound)
Allow ESTABLISHED/RELATED (return traffic)
Allow localhost
Allow DNS to whitelisted upstream servers
Allow traffic to Squid proxy port
Block multicast and link-local (169.254.0.0/16, 224.0.0.0/4)
LOG + REJECT UDP
LOG + REJECT everything else
Finding (Medium) — IPv6 container OUTPUT chain lacks DROP rules
containers/agent/setup-iptables.sh ends with:
# Drop all other TCP and UDP traffic (default deny policy)
iptables -A OUTPUT -p tcp -j DROP
iptables -A OUTPUT -p udp -j DROP
There are no corresponding ip6tables OUTPUT DROP rules. Also, in src/host-iptables.ts, the CHAIN_NAME_V6 chain is created only when IPv6 DNS servers are configured (hasIpv6Dns && ip6tablesAvailable) and is never inserted into ip6tables DOCKER-USER:
// Only IPv4 DOCKER-USER insertion (src/host-iptables.ts)awaitexeca('iptables',['-t','filter','-I','DOCKER-USER','1','-i',bridgeName,'-j',CHAIN_NAME,]);// No corresponding ip6tables DOCKER-USER insertion
If a Docker network is created with IPv6 support, IPv6 TCP/UDP from the agent container would be unfiltered. Docker disables IPv6 on user-defined bridge networks by default, limiting practical risk, but this is a defense-in-depth gap.
Finding (Low) — ICMP not blocked in container OUTPUT chain
containers/agent/setup-iptables.sh drops tcp and udp but not icmp/icmpv6. Raw ICMP packets (ping, unreachable messages) from the container are not filtered. While ICMP cannot carry application-layer payloads directly, ICMP tunneling tools (e.g., ptunnel) can encapsulate TCP over ICMP. The host-level chain also does not explicitly drop ICMP:
// src/host-iptables.ts — REJECT rules'-j','REJECT','--reject-with','icmp-port-unreachable'// Only applies to multicast and link-local matches, not general ICMP OUTPUT
Recommendation: Add iptables -A OUTPUT -p icmp -j DROP and ip6tables -A OUTPUT -p icmpv6 -j DROP in setup-iptables.sh after the TCP/UDP DROP rules (or use a policy drop on the OUTPUT chain).
Container Security Assessment
Capabilities: partially hardened, with a necessary window.
The agent container is granted SYS_CHROOT and SYS_ADMIN for startup operations, then drops them via capsh before executing user code (containers/agent/entrypoint.sh:307, src/docker-manager.ts:1012). NET_ADMIN is deliberately withheld from the agent; a separate init container (awf-iptables-init) handles iptables setup via shared network namespace.
The rationale (procfs mounting for .NET CLR) is documented, but apparmor:unconfined removes Docker's default AppArmor policy, which normally blocks:
Arbitrary mounts
Write access to /proc/sysrq-trigger, /proc/kcore
Access to host devices
Some ptrace vectors
Combined with SYS_ADMIN being temporarily available during startup and seccomp allowing mount/unshare/setns, this creates an elevated container escape window.
Finding (Medium) — open_by_handle_at in seccomp allowlist
// containers/agent/seccomp-profile.json (in the SCMP_ACT_ALLOW block)"open_by_handle_at",
This syscall was exploited in CVE-2014-5155 ("Shocker" container escape), allowing processes to open host filesystem files by inode handle even when path-based access is restricted. When SYS_ADMIN is held (startup window) AND AppArmor is unconfined, open_by_handle_at can open inodes on any mounted filesystem. Modern kernel mitigations (fsopen, mount namespaces) reduce but don't eliminate this risk. Recommendation: explicitly deny open_by_handle_at in seccomp (add to SCMP_ACT_ERRNO block alongside ptrace).
Seccomp: unshare, mount, setns allowed
// SCMP_ACT_ALLOW list includes: "mount", "unshare", "setns"````mount` is needed for procfs. `unshare` is needed by some tools (sandboxed builds). `setns` is needed by some container runtimes. These are difficult to remove without breaking legitimate use cases, but they represent container escape primitives that rely on capability checks for safety. The seccomp profile's `SCMP_ACT_ERRNO` block correctly blocks `kexec_load`, `init_module`, `pivot_root`, `add_key`, `keyctl`, which are the most dangerous escalation paths.### Domain Validation Assessment**Domain validation is robust.** `src/domain-patterns.ts` validates against:- Empty patterns- Bare wildcard `*`- Overly broad `*.*`- Patterns with only wildcards and dots- Double-dot sequences- Multiple wildcard segmentsWildcard conversion uses character class `[a-zA-Z0-9.-]*` instead of `.*` (ReDoS mitigation), anchored with `^` and `$`. Input length is capped at 512 characters before regex execution.Protocol-specific allowlisting (`(domain/redacted) vs `(domain/redacted)`) is implemented with separate Squid ACLs and correct `!CONNECT`/`CONNECT` rule selection.**Raw IP bypass is blocked.** Squid config generates:```acl dst_ipv4 dstdom_regex ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$acl dst_ipv6 dstdom_regex ^\[?[0-9a-fA-F:]+\]?$http_access deny dst_ipv4http_access deny dst_ipv6
$ grep -n "iptables.*OUTPUT.*DROP\|ip6tables.*OUTPUT.*DROP" containers/agent/setup-iptables.sh
319:iptables -A OUTPUT -p tcp -j DROP
320:iptables -A OUTPUT -p udp -j DROP
# No ip6tables OUTPUT DROP rules found
AppArmor disabled in docker-manager.ts
$ grep -n "apparmor" src/docker-manager.ts
1022: // AppArmor is set to unconfined to allow mounting procfs at /host/proc
1028: 'apparmor:unconfined',
squid_ports= [22,23,25,110,143,445,1433,1521,3306,3389,5432,5984,6379,6984,8086,8088,9200,9300,27017,27018,28017]
iptables_ports= [22,23,25,110,143,445,1433,1521,3306,3389,5432,6379,27017,27018,28017]
# In squid but not iptables (no explicit NAT RETURN rule): [5984, 6984, 8086, 8088, 9200, 9300]# Blocked by final OUTPUT DROP, not by explicit RETURN rule```
</details><details><summary>npmauditresults</summary>```
vulnerabilities: {info: 0, low: 0, moderate: 4, high: 0, critical: 0 }MODERATE: js-yamlMODERATE: markdown-itMODERATE: markdownlintMODERATE: markdownlint-cli2
All moderate — only affect dev/docs tooling, not runtime security.
✅ Recommendations
🔴 Medium Priority
M1. Add IPv6 DROP rules in container OUTPUT chain (containers/agent/setup-iptables.sh)
Add after the existing TCP/UDP DROP rules:
# Drop all IPv6 traffic (defense-in-depth; IPv6 is not used in AWF network)if [ "$IP6TABLES_AVAILABLE"=true ];then
ip6tables -A OUTPUT -p tcp -j DROP
ip6tables -A OUTPUT -p udp -j DROP
ip6tables -A OUTPUT -p icmpv6 -j DROP
fi
Also insert CHAIN_NAME_V6 into ip6tables DOCKER-USER in src/host-iptables.ts for host-level coverage.
M2. Deny open_by_handle_at in seccomp (containers/agent/seccomp-profile.json)
Move open_by_handle_at from the ALLOW block to the explicit ERRNO block (alongside ptrace):
{
"names": ["open_by_handle_at", "name_to_handle_at"],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "Block file-handle syscalls used in Shocker-style container escapes (CVE-2014-5155)"
}
Verify that no agent use case requires this syscall before applying.
🟡 Low Priority
L1. Block ICMP in container OUTPUT (containers/agent/setup-iptables.sh)
iptables -A OUTPUT -p icmp -j DROP
Add before the final TCP/UDP DROP rules. This prevents ICMP tunneling and reduces the covert channel surface.
L2. Enable DLP by default for sensitive deployments
Consider making --enable-dlp default-on for CI/CD environments where credential leakage is a high-risk scenario. The current opt-in model relies on operators knowing to enable it.
L3. Fix API proxy iptables port range (src/host-iptables.ts:365–373)
Replace contiguous range with discrete multiport rules:
Add ports 5984, 6984, 8086, 8088, 9200, 9300 to the DANGEROUS_PORTS array in setup-iptables.sh to match squid-config.ts. Currently these are only blocked by the final OUTPUT DROP rule rather than the explicit RETURN-then-DROP path.
🟢 Informational
I1. Consider image digest pinning — GHCR images are pulled by tag (latest or version). Pinning to SHA256 digest would prevent tag mutation attacks.
I2. Document AppArmor trade-off — The reason for apparmor:unconfined (procfs mount for .NET CLR) is documented in code comments but could benefit from a dedicated security decision record.
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 the AWF (Agentic Workflow Firewall) codebase with evidence gathered by direct source inspection. The firewall's defense-in-depth architecture is generally sound, with multiple overlapping layers. No critical vulnerabilities were found. Four medium-severity findings and five low/informational findings are documented below with verifiable evidence.
Security Posture Overview
dstdomain+dstdom_regex, ReDoS-safe wildcardsdst_ipv4/dst_ipv6ACLs deny raw IP CONNECTscapsh --dropbefore user code; UID ≠ 0 enforcedapparmor:unconfinedto permit procfs mount🔍 Findings from Previous Security Workflow Runs
The
security-reviewandsecret-digger-*workflows are all compiled and active, but log downloads were unavailable in this run due to the environment not having git-based remote access. Findings below are based entirely on static source analysis.🛡️ Architecture Security Analysis
Network Security Assessment
Squid ACL rule ordering is correct. Evidence from
src/squid-config.ts:541–565:Squid evaluates first-match, so non-allowed domains are denied before the
allow localnetrule is reached. ✅Host-level iptables chain is well-structured.
src/host-iptables.tsbuilds a dedicatedFW_WRAPPERchain inserted intoDOCKER-USER, scoping rules to the specific bridge interface. Order:169.254.0.0/16,224.0.0.0/4)Finding (Medium) — IPv6 container OUTPUT chain lacks DROP rules
containers/agent/setup-iptables.shends with:# Drop all other TCP and UDP traffic (default deny policy) iptables -A OUTPUT -p tcp -j DROP iptables -A OUTPUT -p udp -j DROPThere are no corresponding
ip6tablesOUTPUT DROP rules. Also, insrc/host-iptables.ts, theCHAIN_NAME_V6chain is created only when IPv6 DNS servers are configured (hasIpv6Dns && ip6tablesAvailable) and is never inserted into ip6tables DOCKER-USER:If a Docker network is created with IPv6 support, IPv6 TCP/UDP from the agent container would be unfiltered. Docker disables IPv6 on user-defined bridge networks by default, limiting practical risk, but this is a defense-in-depth gap.
Finding (Low) — ICMP not blocked in container OUTPUT chain
containers/agent/setup-iptables.shdropstcpandudpbut noticmp/icmpv6. Raw ICMP packets (ping, unreachable messages) from the container are not filtered. While ICMP cannot carry application-layer payloads directly, ICMP tunneling tools (e.g.,ptunnel) can encapsulate TCP over ICMP. The host-level chain also does not explicitly drop ICMP:Recommendation: Add
iptables -A OUTPUT -p icmp -j DROPandip6tables -A OUTPUT -p icmpv6 -j DROPinsetup-iptables.shafter the TCP/UDP DROP rules (or use a policy drop on the OUTPUT chain).Container Security Assessment
Capabilities: partially hardened, with a necessary window.
The agent container is granted
SYS_CHROOTandSYS_ADMINfor startup operations, then drops them viacapshbefore executing user code (containers/agent/entrypoint.sh:307,src/docker-manager.ts:1012). NET_ADMIN is deliberately withheld from the agent; a separate init container (awf-iptables-init) handles iptables setup via shared network namespace.The
ptrace/process_vm_readv/process_vm_writevsyscalls are explicitly blocked in the seccomp profile atcontainers/agent/seccomp-profile.json.Finding (Medium) — AppArmor disabled for agent container
The rationale (procfs mounting for .NET CLR) is documented, but
apparmor:unconfinedremoves Docker's default AppArmor policy, which normally blocks:/proc/sysrq-trigger,/proc/kcoreCombined with
SYS_ADMINbeing temporarily available during startup and seccomp allowingmount/unshare/setns, this creates an elevated container escape window.Finding (Medium) —
open_by_handle_atin seccomp allowlistThis syscall was exploited in CVE-2014-5155 ("Shocker" container escape), allowing processes to open host filesystem files by inode handle even when path-based access is restricted. When
SYS_ADMINis held (startup window) AND AppArmor is unconfined,open_by_handle_atcan open inodes on any mounted filesystem. Modern kernel mitigations (fsopen, mount namespaces) reduce but don't eliminate this risk. Recommendation: explicitly denyopen_by_handle_atin seccomp (add to SCMP_ACT_ERRNO block alongside ptrace).Seccomp:
unshare,mount,setnsallowedInput Validation Assessment
Shell escaping is correct.
src/cli.ts:862–876:Single-quote wrapping with escaped inner single-quotes is safe against shell injection.
UID/GID validation is present.
containers/agent/entrypoint.sh:10–29validates that AWF_USER_UID/GID are numeric and non-zero (prevents root mapping):setup-iptables.sh: no ip6tables OUTPUT DROP;host-iptables.ts: no ip6tables DOCKER-USER insertionopen_by_handle_at+ SYS_ADMIN window + AppArmor unconfinedseccomp-profile.jsonallowsopen_by_handle_at;docker-manager.ts:1028apparmor:unconfinedsetup-iptables.sh: only drops tcp/udp; no icmp DROP rulesrc/dlp.ts;--enable-dlpnot default;src/redact-secrets.tsonly covers command stringssrc/host-iptables.ts:365–373:minPort:maxPort=10000:10004(contiguous range includes 10003)squid-config.tsDANGEROUS_PORTS includes 5984,6984,8086,8088,9200,9300;setup-iptables.shdoes not--enable-host-accesssetup-iptables.sh:170–210: host gateway and network gateway bypass Squid for ports 80/443src/squid-config.ts:read_timeout 30 minutes,connect_timeout 30s— very long timeouts🎯 Attack Surface Map
--allow-domainssrc/cli.ts:1318validateDomainOrPattern(), ReDoS-safe wildcards--env/-esrc/docker-manager.ts:373--env-allcopies entireprocess.envminus exclusions--allow-host-portssrc/squid-config.ts:460containers/agent/setup-iptables.shsrc/squid-config.ts:240containers/api-proxy/server.js:28containers/agent/entrypoint.sh:303containers/agent/seccomp-profile.jsonopen_by_handle_atallowed;unshare/mount/setnsallowedsrc/docker-manager.ts:850📋 Evidence Collection
IPv6 container OUTPUT chain – missing DROP rules
AppArmor disabled in docker-manager.ts
open_by_handle_at in seccomp allowlist
API proxy port range (10000:10004 includes 10003)
Dangerous ports mismatch
All moderate — only affect dev/docs tooling, not runtime security.
✅ Recommendations
🔴 Medium Priority
M1. Add IPv6 DROP rules in container OUTPUT chain (
containers/agent/setup-iptables.sh)Add after the existing TCP/UDP DROP rules:
Also insert
CHAIN_NAME_V6intoip6tables DOCKER-USERinsrc/host-iptables.tsfor host-level coverage.M2. Deny
open_by_handle_atin seccomp (containers/agent/seccomp-profile.json)Move
open_by_handle_atfrom the ALLOW block to the explicit ERRNO block (alongside ptrace):{ "names": ["open_by_handle_at", "name_to_handle_at"], "action": "SCMP_ACT_ERRNO", "errnoRet": 1, "comment": "Block file-handle syscalls used in Shocker-style container escapes (CVE-2014-5155)" }Verify that no agent use case requires this syscall before applying.
🟡 Low Priority
L1. Block ICMP in container OUTPUT (
containers/agent/setup-iptables.sh)Add before the final TCP/UDP DROP rules. This prevents ICMP tunneling and reduces the covert channel surface.
L2. Enable DLP by default for sensitive deployments
Consider making
--enable-dlpdefault-on for CI/CD environments where credential leakage is a high-risk scenario. The current opt-in model relies on operators knowing to enable it.L3. Fix API proxy iptables port range (
src/host-iptables.ts:365–373)Replace contiguous range with discrete multiport rules:
L4. Sync dangerous ports lists (
containers/agent/setup-iptables.sh)Add ports 5984, 6984, 8086, 8088, 9200, 9300 to the DANGEROUS_PORTS array in
setup-iptables.shto matchsquid-config.ts. Currently these are only blocked by the final OUTPUT DROP rule rather than the explicit RETURN-then-DROP path.🟢 Informational
I1. Consider image digest pinning — GHCR images are pulled by tag (
latestor version). Pinning to SHA256 digest would prevent tag mutation attacks.I2. Document AppArmor trade-off — The reason for
apparmor:unconfined(procfs mount for .NET CLR) is documented in code comments but could benefit from a dedicated security decision record.📈 Security Metrics
Beta Was this translation helpful? Give feedback.
All reactions