Skip to content

Add vendor-only egress and phase policy (D3) - #44

Merged
mchwang merged 20 commits into
mainfrom
codex/agent-isolation-d3
Sep 25, 2026
Merged

mchwang merged 20 commits into
mainfrom
codex/agent-isolation-d3

Conversation

@mchwang

@mchwang mchwang commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem and resulting behavior

D2 isolates agents in pinned containers, but those containers still need a narrowly controlled path to the selected model vendor and a phase-specific tool policy. This change adds D3 / T2: each agent runs on an internal Docker network whose CONNECT-only proxy permits only the pinned vendor hosts, while Claude/Codex web and MCP access are disabled and phase policies deny writes and command dispatch where the plan requires.

This PR is stacked on #40 (codex/agent-isolation-d2) and should be reviewed as the D3 increment only.

Scope

  • Add ownership-bound internal vendor networks and a locked-down proxy container.
  • Pin Claude to api.anthropic.com; pin Codex to api.openai.com and chatgpt.com.
  • Attach agent containers only to the internal network and validate the daemon-resolved attachment and proxy environment before start.
  • Add trusted phase policies:
    • planning/questions: read, list, and search only; read-only worktree; no runner command
    • review: read-only worktree; exact approved runner argv only
    • execute/fix: writable worktree plus exact approved runner argv
  • Disable Claude Bash/WebFetch/WebSearch/NotebookEdit and all MCP servers; disable Codex web search and MCP servers.
  • Add real-Docker allow/deny coverage, all-phase worktree checks, and authenticated Codex/Claude probes through the restricted network.

Owned files:

  • .github/workflows/agent-isolation.yml
  • agents/container/Dockerfile
  • agents/container/image.ts
  • agents/container/profile.ts
  • agents/container/run.ts
  • agents/network/network.ts
  • agents/network/proxy.mjs
  • agents/policy.ts
  • test/agent-container.test.ts
  • test/agent-network.test.ts
  • test/agent-policy.test.ts
  • test/questions.test.ts

Validation

Validated head: 981e831637c1d19c3b4c759e18cc5ec06e8a5f33

  • npm run typecheck
  • npx vitest run --no-file-parallelism — 16 files, 255 tests passed (serialized locally to avoid Docker resource contention; CI runs the default command)
  • CLAUDE_CODE_OAUTH_TOKEN=... CODEBOOST_RUN_AUTH_PROBES=1 CODEBOOST_CODEX_AUTH_FILE=... npx vitest run test/agent-container.test.ts — 23 tests passed, including live Codex and Claude startup through vendor-only egress
  • Real-Docker network test proves the vendor host is reachable, an unrelated proxy target returns 403, and direct egress fails.

Dependencies and deferrals

  • Depends on D2 / D2: Add pinned restricted agent containers #40 and remains intentionally stacked until that prerequisite lands.
  • D4 owns the production Claude/Codex adapters, cancellation settlement, and bounded output.
  • D5 owns the combined real-Docker and hostile-input boundary gate.
  • No merge authorization has been given.

Review record

  • Pre-review and round-1 self-review: full D3 diff inspected; no remaining local findings.
  • Round 1: accepted all six findings. Container commands are now policy-generated opaque capabilities; every invocation owns a separate validated network; launch revalidates the proxy, network, subnet, and endpoint set; Claude receives an exact tool set; Codex shell is disabled. Added copied-command, cross-invocation, concurrent-network, and endpoint-mutation regressions. Nothing declined.
  • Round 2: accepted all five findings. Command capabilities now include exact vendor identity; both CLI builders reject the wrong vendor; proxy validation covers namespace/device state; network validation consumes the caller deadline. Added vendor-mismatch, proxy-replacement, and timeout regressions. Nothing declined.
  • Round 3: accepted all three findings. Proxy validation now requires the exact entrypoint and environment, binds the stored proxy address, and agent containers disable normal DNS while using that validated address. Added proxy-environment, DNS-mutation, and direct embedded-resolver regressions. Nothing declined.
  • Round 4: accepted all four findings. Container profiles now exclusively own and dispose their vendor network; the full agent container is revalidated immediately before start; proxy and agent validation reject daemon DNS, host and published-port overrides. Added cleanup, final-attachment and daemon-mutation regressions. Nothing declined.
  • Round 5: no new findings. Copilot confirmed all four round-4 findings resolved on 981e831.
  • Review lesson audit: command-policy binding is covered by the existing phase-enforcement and exact-argv rules; launch-time network revalidation and per-invocation network ownership are concrete applications of the existing external-state and isolation rules. No new broadly reusable rule is needed before merge.

Closes the D3 increment tracked in #28; it does not close the full lane.

Copilot AI lite review requested due to automatic review settings September 24, 2026 17:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical policy enforcement, network validation, and isolation issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 6 High severity

Open (6)
What changed in this PR

Adds D3 vendor-only Docker egress and phase-specific agent policies, with expanded integration and policy tests.

Changes:

  • Adds restricted vendor proxy networks.
  • Enforces phase-specific tool and worktree controls.
  • Expands Docker, network, policy, and CI validation.
  • Critical review findings remain unresolved.
File Description
test/​agent-policy.test.ts Tests phase policy enforcement
test/​agent-network.test.ts Tests vendor-only egress
test/​agent-container.test.ts Tests container isolation
agents/​policy.ts Defines phase and vendor controls
agents/​network/​proxy.mjs Implements CONNECT filtering
agents/​network/​network.ts Creates and manages vendor networks
agents/​container/​run.ts Validates network and proxy settings
agents/​container/​profile.ts Attaches networks and policies
agents/​container/​image.ts Updates Docker build context
agents/​container/​Dockerfile Includes the egress proxy
.github/​workflows/​agent-isolation.yml Runs expanded isolation tests

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread agents/container/profile.ts Outdated
Comment thread agents/container/run.ts
Comment thread agents/network/network.ts Outdated
Comment thread agents/network/network.ts Outdated
Comment thread agents/policy.ts Outdated
Comment thread agents/policy.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread agents/container/profile.ts Outdated
Comment thread agents/network/network.ts Outdated
Comment thread agents/policy.ts
Comment thread agents/policy.ts
Comment thread agents/network/network.ts Outdated
Copilot AI review requested due to automatic review settings September 24, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical network-hardening findings and deadline-handling issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity

Open (3)
Resolved since last review (5)

Comment thread agents/network/network.ts Outdated
Comment thread agents/network/network.ts Outdated
Comment thread agents/network/network.ts Outdated
Copilot AI review requested due to automatic review settings September 24, 2026 18:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Six unresolved findings, including four critical issues, block approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 4 High severity

Open (4)
Resolved since last review (3)

Comment thread agents/container/profile.ts Outdated
Comment thread agents/container/run.ts Outdated
Comment thread agents/network/network.ts Outdated
Comment thread agents/network/network.ts
Copilot AI review requested due to automatic review settings September 24, 2026 19:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Unresolved moderate findings cover network cleanup behavior and weakened authenticated probe assertions.

Review effort: Lite
Findings: None

Resolved since last review (4)

Copilot AI review requested due to automatic review settings September 25, 2026 06:05
@mchwang
mchwang force-pushed the codex/agent-isolation-d3 branch from 981e831 to f2dab0f Compare September 25, 2026 06:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved moderate findings affect CI serialization, cleanup deadlines, probe validation, and DNS regression coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 Medium severity

Open (3)

Comment thread agents/container/profile.ts Outdated
Comment thread agents/network/network.ts Outdated
Comment thread test/agent-container.test.ts Outdated
@mchwang
mchwang force-pushed the codex/agent-isolation-d2 branch from eccc116 to fb6577c Compare September 25, 2026 06:37
Copilot AI review requested due to automatic review settings September 25, 2026 06:51
@mchwang
mchwang force-pushed the codex/agent-isolation-d3 branch from f2dab0f to 5a1c75f Compare September 25, 2026 06:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Four moderate issues remain around cleanup deadlines, resource ownership, and proxy connection handling.

Review effort: Lite
Findings: None

Resolved since last review (3)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical and moderate findings remain unresolved, including network-test timing and validation-path coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)

Comment thread test/agent-network.test.ts Outdated
createVendorNetwork now refuses an expired invocation, so capturing the
suite's invocation at module load with a 60 s deadline could fail the
whole file after a cold image build. Capture it in beforeAll after the
build, with a deadline covering the suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 25, 2026 18:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved CI coverage and network/proxy validation findings remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Validate the proxy uses a non-restarting Docker policy

agents/​network/​network.ts:78

The proxy's daemon-resolved configuration does not validate HostConfig.RestartPolicy, even though the agent validator rejects automatic restart policies. A proxy mutated with docker update --restart=always would pass assertVendorNetwork; after a crash or failed cleanup Docker could relaunch the egress endpoint and keep it alive beyond the invocation. Require the resolved policy to be the default non-restarting policy (Name empty/no and retry count zero) before accepting the proxy.

Comment thread agents/network/network.ts Outdated
- Record the network and proxy container IDs returned at creation.
  Validation inspects those IDs and requires the network name and proxy
  endpoint to match them, so a removed-and-recreated network or proxy with
  identical attributes is refused. removeVendorNetwork deletes by ID and
  leaves a same-named stand-in in place, which surfaces as a cleanup
  failure.
- Require the proxy's default no-restart policy, as for the agent and
  keeper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 25, 2026 18:27
@mchwang

mchwang commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the "previously missed" finding in Copilot's review of 9876b04 (in e90b579):

  • Proxy restart policy (network.ts:78): network validation now requires the proxy's resolved RestartPolicy to be the default (name empty or no, zero retries), matching the agent and keeper checks. A regression runs docker update --restart=always on the proxy and confirms assertVendorNetwork refuses it; the previous code accepted it.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Unresolved moderate findings remain in profile deadline handling and proxy header-size validation.

Review effort: Lite
Findings: None

Resolved since last review (1)

- The egress proxy applied its 8 KiB limit to everything received so far,
  so a CONNECT header followed in the same read by a large ClientHello was
  refused with 431. The limit now covers only the header.
- assertContainerProfile clamps its network revalidation budget to the
  invocation deadline, including for callers using the default budget.
- Give the late-keeper regression enough budget to pass on a loaded daemon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang

mchwang commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the summary-only findings in Copilot's review of e90b579 ("profile deadline handling and proxy header-size validation"). The review had no inline findings, so I checked both areas myself; both had real issues, fixed in e8e8d57:

  • Proxy header-size validation: the 8 KiB limit was applied to everything received so far, not just the CONNECT header. Once early tunnel bytes were forwarded (c31e790), a header followed in the same read by a large TLS ClientHello was wrongly refused with 431. The limit now applies only to the header. New tests send a header plus 16 KiB of payload in one write, which the previous proxy refused and the fixed one forwards intact, and confirm that a header over 8 KiB is still refused with 431.
  • Profile deadline handling: assertContainerProfile passed its default 30 s budget straight to network revalidation. The launch paths already passed a clamped budget, but a caller relying on the default could still validate after the invocation deadline. The budget is now clamped with profileTimeout, so every caller refuses once the deadline has passed. A regression waits past the deadline and calls it with the default budget; the previous code accepted it.

I also gave the late-keeper storage regression a budget that tolerates a loaded Docker daemon. It still fails against the storage code from before the fix.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings September 25, 2026 18:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Unresolved critical cleanup and proxy-buffering findings remain, along with a timeout-budget issue.

Review effort: Lite
Findings: 2 High severity

Open (2)

Comment thread agents/network/network.ts Outdated
Comment thread agents/network/proxy.mjs Outdated
- createVendorNetwork keeps the network and proxy IDs as soon as each
  create returns and uses them for the rest of setup and for failure
  cleanup. Names are used only for a create whose ID never came back, so
  a same-named stand-in carrying the allocation label is not deleted.
- The proxy searches for the header end only within the 8 KiB limit and
  never keeps more than an unfinished header between reads, so a streamed
  unterminated header cannot grow its buffer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 25, 2026 19:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Authenticated probes bypass validated launch helpers, and network teardown can mask setup failures.

Review effort: Lite
Findings: None

Resolved since last review (2)

- The opt-in authenticated Codex and Claude probes now launch through
  runContainer, so they exercise creation, validation and removal instead
  of raw docker create and start. The Claude token is passed only as the
  profile's secret.
- The network suite skips teardown when setup never produced a network,
  so a setup failure is reported instead of a teardown TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang

mchwang commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the summary-only notes in Copilot's review of 647a0bb (no inline findings), both in 75fdee9:

  • Authenticated probes bypass validated launch helpers: the opt-in live Codex and Claude probes (CODEBOOST_RUN_AUTH_PROBES=1) now run through runContainer instead of raw docker create/docker start, so they exercise creation, pre-start validation and removal. The Claude token goes only through the profile's secrets. These probes need real credentials and did not run in this change; typecheck covers them.
  • Network teardown can mask setup failures: the network suite's afterAll now skips removal when beforeAll never produced a network, so a setup failure is reported instead of a teardown TypeError.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings September 25, 2026 19:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical cleanup-capability and moderate workflow, network, proxy, and probe findings.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)

Comment thread agents/container/run.ts
removeContainerOrThrow now refuses a profile that the trusted builder did
not register before running any Docker command, so a copied profile's
public name and ownership label cannot remove another invocation's
container. The launch paths already rejected copies through their first
profileTimeout call; this makes the cleanup boundary enforce it itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 25, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Unresolved findings remain around cleanup deadlines, profile resource leaks, the authenticated Codex probe, and CI ownership.

Review effort: Lite
Findings: None

Resolved since last review (1)

@mchwang
mchwang merged commit f8c8d9f into main Sep 25, 2026
4 checks passed
@mchwang
mchwang deleted the codex/agent-isolation-d3 branch September 25, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants