feat: add --security-mode strict|compat with strict as default#6197
Conversation
Introduce a --security-mode flag that bundles AWF's security posture into
two modes: strict (default) and compat (legacy opt-in).
Strict mode (default) enforces:
- Network isolation (Docker internal network, Squid dual-homed)
- API proxy (credential injection, agent never holds keys)
- Rejects --enable-host-access, --enable-dind, --dns-over-https
When incompatible options are passed in strict mode, they are overridden
with a warning that tells operators to use --security-mode compat:
warning: --enable-host-access was ignored (incompatible with
--security-mode strict, the default).
Pass --security-mode compat to enable host access.
Compat mode preserves the legacy iptables-based configuration for
operators who need host-access, DinD, or direct credential passing.
Changes:
- Add securityMode to SecurityOptions type, CLI options, config file,
JSON schema, and config mapper
- Add applySecurityMode() in security-mode.ts: enforces strict defaults
and overrides incompatible options with warnings
- Wire into config-assembly pipeline after buildConfig(), before validators
- Remove 'experimental' label from network-isolation (now the default)
- Update error messages ('not yet supported' → 'not supported')
- Add 10 unit tests for strict/compat mode behavior
- Update existing test fixtures to use securityMode: 'compat' so they
preserve their original behavior
Closes #6193
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (1 files)
✨ New Files (1 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Pull request overview
Introduces default strict security mode, bundling topology isolation and API-proxy credential handling while retaining opt-in compatibility mode.
Changes:
- Adds CLI, config, type, and schema support for security modes.
- Implements strict-mode overrides and warnings.
- Updates validator wiring and tests.
Show a summary per file
| File | Description |
|---|---|
src/types/security-options.ts |
Defines the security-mode type. |
src/config-mapper.ts |
Maps file configuration to CLI options. |
src/config-file.ts |
Adds file-config typing. |
src/commands/validators/security-mode.ts |
Implements mode enforcement. |
src/commands/validators/security-mode.test.ts |
Tests strict and compatibility behavior. |
src/commands/validators/infrastructure-validator.ts |
Updates topology validation messages. |
src/commands/validators/config-assembly.ts |
Adds security enforcement to validation. |
src/commands/validators/config-assembly.test-utils.ts |
Preserves compatibility-mode fixtures. |
src/commands/validators/config-assembly-flags.test.ts |
Updates topology validation tests. |
src/commands/build-config.ts |
Builds the security-mode setting. |
src/cli-options.ts |
Adds the new CLI flag. |
src/awf-config-schema.json |
Extends the runtime schema. |
docs/awf-config.schema.json |
Extends the published schema. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 13/13 changed files
- Comments generated: 5
- Review effort level: Medium
| if (config.enableHostAccess) { | ||
| logger.warn( | ||
| '⚠️ --enable-host-access was ignored (incompatible with --security-mode strict, the default).\n' + | ||
| ' Pass --security-mode compat to enable host access.', | ||
| ); | ||
| config.enableHostAccess = false; |
| if (!config.networkIsolation) { | ||
| if (config.networkIsolation === false) { | ||
| // Explicitly set to false via CLI or config — warn and override | ||
| logger.warn( |
| // Force network-isolation on | ||
| if (!config.networkIsolation) { | ||
| if (config.networkIsolation === false) { | ||
| // Explicitly set to false via CLI or config — warn and override | ||
| logger.warn( | ||
| '⚠️ network.isolation: false was ignored (incompatible with --security-mode strict, the default).\n' + | ||
| ' Pass --security-mode compat to disable network isolation.', | ||
| ); | ||
| } | ||
| config.networkIsolation = true; |
| '--security-mode <mode>', | ||
| 'Security enforcement mode (default: strict).\n' + | ||
| ' strict: network-isolation + api-proxy, no sudo/iptables.\n' + | ||
| ' compat: legacy iptables mode, requires sudo.', | ||
| 'strict' |
| false | ||
| ) | ||
| .option( | ||
| '--security-mode <mode>', |
|
@copilot address review feedback |
The legacy sudo/iptables integration test path needs compat mode now that strict is the default. Without this, strict mode forces network-isolation on, which is incompatible with the sudo/iptables flow and causes exit code -1. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
✅ Copilot review passed with no inline comments. @lpcox Add the |
Switch all 4 chroot test suites (procfs, edge-cases, languages, package-managers) from runWithSudo() (legacy iptables/sudo) to run() (strict mode: network-isolation, no sudo). Changes: - batch-runner.ts: use runner.run() instead of runner.runWithSudo() - chroot-edge-cases.test.ts: 10 calls migrated - chroot-package-managers.test.ts: 11 calls migrated - chroot-languages.test.ts: 3 calls migrated - chroot-procfs.test.ts: already batch-only, no changes needed - awf-runner.ts: update runWithSudo() docstring to mark as legacy - test-chroot.yml: remove stale sudo-related comments The non-chroot integration tests still use runWithSudo() with --security-mode compat and will be migrated separately. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Add Commander .choices(['strict','compat']) to --security-mode so typos like --security-mode comapt fail with a clear error instead of silently falling through to strict-mode logic (#cli-options.ts) - Register --no-network-isolation and --no-enable-api-proxy CLI options so Commander accepts them; drop the hardcoded false defaults so the value is undefined when neither flag is passed. undefined signals "not set by user", false signals "explicitly disabled" — downstream strict-mode warnings now only fire for genuine user overrides (#cli-options.ts, build-config.ts) - Skip Docker network-isolation enforcement in strict mode for microVM runtimes (containerRuntime where runtimeUsesComposeAgent returns false, e.g. sbx); those VMs enforce isolation at the hypervisor layer via DOCKER_SANDBOXES_PROXY (#security-mode.ts) - Clear allowHostServicePorts in strict mode alongside enableHostAccess; applyHostServicePortsConfig() runs later in the pipeline and would auto- re-enable host access if the port list is left intact (#security-mode.ts) - Update security-mode.test.ts: split networkIsolation/enableApiProxy tests into undefined-default (silent enable) and explicit-false (warn+enable) cases; add allowHostServicePorts clearing tests; add microVM runtime tests
Addressed all five review points in commit
All 3,748 tests pass. |
When strict mode disables --enable-host-access, the dependent --allow-host-ports and --allow-host-service-ports flags must also be cleared. Otherwise the downstream validator sees host ports without host access enabled and calls process.exit(1). This was triggered by allowDomains: ['localhost'] which auto-enables host access and host ports — strict mode overrode enableHostAccess but left the port list, causing: ❌ --allow-host-ports requires --enable-host-access to be set Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Migrate all compatible integration tests from runWithSudo() to run(), which uses the new default strict security mode (network-isolation). 24 files migrated (216 runWithSudo → run calls): - api-proxy (4 files): proxy routing tests - domain/dns (4 files): domain filtering, DNS config - utility (16 files): env vars, exit codes, volumes, git, etc. 5 files remain on compat mode (runWithSudo) by design: - network-security.test.ts: iptables behavior verification - ipv6.test.ts: ip6tables rules - localhost-access.test.ts: allowHostPorts testing - host-tcp-services.test.ts: allowHostServicePorts testing - credential-hiding.test.ts: /dev/null mount testing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
Strict security mode uses network-isolation (Docker internal network) instead of host iptables, so sudo is no longer needed to run AWF. Updated 10 lock files: - smoke-gvisor (4 files), smoke-chroot (1 file) - secret-digger (3 files) - schema-sync, model-api-mapping-updater Setup steps (apt-get install, sysctl, chmod) retain sudo as expected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
Strict security mode uses network-isolation with MCP gateway for API traffic, so --enable-host-access and --allow-host-ports are no longer needed. These flags would be silently overridden at runtime anyway. smoke-services.lock.yml intentionally kept on compat mode (needs host service port access for Redis/PostgreSQL GitHub Actions services). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
Reverts 218bb61 and 534a612. The lock file changes (removing sudo and --enable-host-access) require the strict-mode AWF to be released first. The installed AWF on runners (v0.82.8) still defaults to iptables mode, which needs sudo. These changes will be applied in a follow-up PR after the next release. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
|
📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅ |
|
✅ Smoke Claude passed |
|
✅ Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓 |
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded. |
|
🔌 Smoke Services — All services reachable! ✅ |
|
✅ Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓 |
🧪 Smoke Test: Copilot PAT Auth
Overall: PARTIAL — MCP ✅ but Auth mode: PAT (COPILOT_GITHUB_TOKEN) | @lpcox Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test: API Proxy OpenTelemetry Tracing
All scenarios passed. OTEL tracing integration is fully operational. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test: Services Connectivity
Overall: FAIL The AWF sandbox has no route to the host network — Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
Merged PRs reviewed: Deduplicate baseline agent-volume test config fixture; Refactor network setup tests to use a shared DNS/proxy mock fixture\nChecks: merged PR review ✅, GitHub query ✅, GitHub PR list ✅, browser title ✅, file write ✅, discussion lookup ✅, build ✅\nOverall status: PASS Warning Firewall blocked 2 domainsThe following domains were blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"
- "registry.npmjs.org"See Network Configuration for more information.
|
Chroot Version Comparison
Not all runtimes matched — Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
@lpcox Smoke test results for Copilot BYOK (Direct, Azure OpenAI Foundry, Entra):
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra Overall: PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Smoke Test: Gemini Engine Validation
Overall status: PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "localhost"See Network Configuration for more information.
|
🔬 Smoke Test Results — Docker Sbx
PR: feat: add --security-mode strict|compat with strict as default Overall: PARTIAL — MCP confirmed working; pre-computed step outputs (
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — ✅ PASS Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
PR Titles: ✅ GitHub MCP connectivity Overall: PASS cc @lpcox Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
Replace nslookup with dig for DNS resolution tests. In network- isolation mode, Docker's embedded DNS (127.0.0.11) works with dig but nslookup has known issues with SERVFAIL responses. The dig command provides reliable DNS testing across all security modes. Also migrates DNS exfiltration tests to use dig @<server> which properly tests that external DNS IPs are unreachable from the internal network. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
Rewrite one-shot-tokens and token-unset tests to verify that real auth tokens are NEVER exposed to the agent container. No real token (GITHUB_TOKEN, COPILOT_GITHUB_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY) should ever be visible in the agent environment — the API proxy sidecar holds all credentials and injects them only on upstream requests. Tests now assert: - Real token values never appear in stdout - Real token values never appear in /proc/1/environ - printenv never returns real token values Also bumps volume-mounts timeout from 30s to 60s to accommodate 3-container startup in network-isolation mode. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dcc99fc-3d0e-40c5-8b75-bc43d9bf5dee
…pec CLI mapping Add missing Section 5 CLI mapping entries for two security fields introduced in PRs #6197 and #6209: - security.legacySecurity → --legacy-security - security.securityMode → --security-mode (deprecated) Both fields exist in src/awf-config-schema.json, src/types/security-options.ts, src/config-file.ts, and src/config-mapper.ts but were not listed in the docs/awf-config-spec.md Section 5 CLI Mapping table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds
--security-mode strict|compatwith strict as the default. This bundles AWF's hardened security posture into a single flag instead of requiring operators to know about--network-isolation --enable-api-proxy --topology-attach.Closes #6193
What strict mode enforces
internal: true)--enable-host-access--enable-dind--dns-over-httpsOverride behavior
Incompatible options are overridden with warnings, not hard errors. AWF continues running in strict mode with the option disabled:
Files changed
src/types/security-options.tssecurityModefieldsrc/cli-options.ts--security-modeCLI option (default:strict)src/config-file.tssecurity.securityModeto config file typesrc/config-mapper.tssrc/commands/build-config.tssrc/awf-config-schema.json+docs/src/commands/validators/security-mode.tssrc/commands/validators/config-assembly.tsapplySecurityMode()into pipelinesrc/commands/validators/infrastructure-validator.tssrc/commands/validators/security-mode.test.tssrc/commands/validators/config-assembly*.test*securityMode: 'compat'to existing test fixturesTest results