Skip to content

Introduce --security-mode strict to bundle the modern no-sudo, no-iptables security posture as the default #6193

Description

@lpcox

Problem

AWF has evolved significantly: network-isolation mode, API proxy credential injection, topology-attach for MCP/DIFC peers, and container runtime options (sbx, gVisor) now provide a hardened, sudo-free security posture. But this posture is hidden behind a collection of flags that are all individually opt-in and default to off:

Flag Default Should be standard?
--network-isolation false ✅ Yes — Docker network topology, no iptables, no sudo
--enable-api-proxy false ✅ Yes — agent never holds API credentials
--enable-host-access false ❌ No — incompatible with network isolation
--enable-dind false ❌ No — firewall bypass risk
--topology-attach [] ✅ Yes — required for MCP gateway / DIFC proxy

0 out of 21 smoke tests use --network-isolation. 19 out of 21 use sudo. The "modern" mode has near-zero CI coverage, while the legacy iptables mode is the de facto standard. This is backwards — the hardened mode should be the default, and the legacy mode should require explicit opt-in.

Proposal: --security-mode strict | compat

Introduce a single --security-mode flag. strict is the default. Operators must explicitly opt into --security-mode compat to use the legacy iptables-based configuration.

--security-mode strict (DEFAULT)

Bundles:

  • Network isolation (internal: true Docker network, Squid dual-homed as sole egress)
  • API proxy (credential injection — agent never holds API keys)
  • No sudo (no host iptables, no NET_ADMIN)
  • No host access (rejects --enable-host-access)
  • No --enable-dind (rejects Docker socket passthrough)
  • Topology-attach available for MCP gateway and DIFC proxy peers

For microvm runtimes (sbx), strict mode uses DOCKER_SANDBOXES_PROXY → Squid instead of Docker network topology (sbx already enforces this without iptables).

--security-mode compat (legacy, explicit opt-in)

Preserves:

  • Host iptables egress enforcement (requires sudo)
  • Agent holds API keys directly (no proxy)
  • --enable-host-access available
  • --enable-dind available (with existing warnings)

Strict mode takes precedence

When running in strict mode (the default), incompatible options are overridden and discarded with a clear warning. Strict mode always wins — it cannot be weakened by stale config files or copy-pasted flags from old documentation.

Incompatible options that are overridden in strict mode:

  • --enable-host-access / network.enableHostAccess → ignored
  • --enable-dind / container.enableDind → ignored
  • --dns-over-https (requires direct external connectivity) → ignored
  • Explicit --no-network-isolation or network.isolation: false → ignored
  • Explicit --no-api-proxy or apiProxy.enabled: false → ignored

Warning behavior: AWF logs a warning for each overridden option and continues in strict mode:

warning: --enable-host-access was ignored (incompatible with --security-mode strict, the default).
         Pass --security-mode compat to enable host access.

AWF continues to run with the incompatible option disabled. This prevents stale configurations from silently breaking workflows while making it clear what needs to change.

Behavior summary

Scenario Result
awf --allow-domains x ... Runs in strict mode (default)
awf --security-mode strict ... Runs in strict mode (explicit)
awf --security-mode compat --enable-host-access ... Runs in compat mode with host access
awf --enable-host-access ... Warning: option ignored, runs in strict mode
Config file with network.isolation: false Warning: option ignored, runs in strict mode
awf --security-mode strict --enable-host-access ... Warning: option ignored, runs in strict mode

Runtime compatibility matrix

Validated via #6195 and existing network-isolation-test.lock.yml:

Runtime Execution model Strict enforcement mechanism Status
Standard Docker (runc) compose internal: true network + Squid dual-homed ✅ Working (network-isolation-test.lock.yml)
gVisor (runsc) compose Same + static DNS via patchComposeWithTopologyHosts ✅ Validated in #6195
ARC/DinD compose Same (designed for this — no sudo on ARC) ✅ Documented as supported
Docker sbx microvm DOCKER_SANDBOXES_PROXY → Squid (daemon-level) ✅ Already no-sudo by nature

Key finding: sbx uses a different enforcement mechanism (daemon-level proxy, not Docker network topology). --security-mode strict needs to recognize this and not apply --network-isolation to microvm runtimes — they get equivalent protection through a different path.

MCP gateway compatibility

The MCP gateway (awmg-mcpg) must be launched as a bridge container (not --network host) for topology-attach to work. This is because Docker cannot connect a host-network container to a user-defined bridge.

Working configuration (validated in CI):

docker run -i --rm --network bridge \
  -p 127.0.0.1:${MCP_GATEWAY_PORT}:${MCP_GATEWAY_PORT} \
  --name awmg-mcpg \
  --add-host host.docker.internal:host-gateway \
  ...

With MCP_GATEWAY_DOMAIN="awmg-mcpg" (agent reaches gateway by container name on awf-net).

Codebase assessment

The codebase is well-structured for this transition — moderate effort, not a major refactor.

Architecture (already clean)

  • cli-workflow.ts has a single if/else (28 lines) separating topology vs iptables paths — zero interleaving
  • Compose generation split cleanly in compose-network.ts
  • Services are pluggable via optional-services.ts
  • Feature flag checks concentrated in ~27 non-test conditionals (not scattered through business logic)

Code breakdown by mode

Category Files Lines Notes
Legacy-only (host-iptables) 25 ~3,000 Cleanly isolable, becomes compat-only
Strict-only (topology) 2 ~230 Already implemented and tested
Shared (proxy, squid, agent) ~50 ~5,000 Used by both modes

Implementation phases

Phase Effort Files What
1. Add --security-mode flag (default: strict) 1-2 days cli-options.ts, build-config.ts, infrastructure-validator.ts Parse flag, set strict as default, reject incompatible options with clear errors
2. API proxy unconditional in strict 1 day optional-services.ts, excluded-vars.ts, agent-service.ts Remove 27 conditional if (enableApiProxy) guards for strict path
3. Host-access / DinD rejection ½ day validators, squid-service.ts Hard error when incompatible flags passed in strict mode
4. Legacy code gating 1-2 days host-iptables*.ts, cli-workflow.ts Gate behind --security-mode compat, add sudo/NET_ADMIN fail-stop

Estimated total: 3-4 days, high confidence of no regressions.

Tasks

  • Add --security-mode CLI option and config schema field (default: strict)
  • Implement strict mode: bundle network-isolation + api-proxy + reject host-access/dind
  • Implement incompatible-option override with clear warnings pointing to --security-mode compat
  • Handle microvm runtimes (sbx): skip network-isolation, rely on DOCKER_SANDBOXES_PROXY
  • Gate compat mode: host-iptables, sudo, host-access, DinD passthrough
  • Add sudo/NET_ADMIN capability guards for compat mode fail-stop
  • Add smoke tests for strict mode as default (standard Docker, gVisor, ARC/DinD, sbx)
  • Validate gVisor + strict mode (feat: migrate gVisor smoke tests to strict security mode (no sudo, network-isolation) #6195 — ✅ passing)
  • Migrate existing smoke tests from compat → strict
  • Update documentation (README, environment.md, config schema, troubleshooting)
  • Release with major version bump

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions