Skip to content

fix(server): Antigravity keeps its workspace containment - #13613

Merged
juliusmarminge merged 3 commits into
t3code/codex-turn-mappingfrom
v2/antigravity-containment
Sep 25, 2026
Merged

juliusmarminge merged 3 commits into
t3code/codex-turn-mappingfrom
v2/antigravity-containment

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Antigravity is the one ACP agent T3 gives client file access on purpose, and its handlers confine reads and writes to the workspace and the attachments dir (AntigravityClientFiles.ts). In V2 those handlers stopped doing anything: AntigravityAdapterV2 registered them inside makeRuntime, then wireAcpRuntimeHandlers registered the generic acpReadTextFile/acpWriteTextFile on the same runtime, and effect-acp keeps only the last handler per method (packages/effect-acp/src/client.ts handleReadTextFile/handleWriteTextFile assign coreHandlers.*). Under auto or full access the policy guard allows every read and write, so Antigravity could read or write any path the T3 server can.

This is layer 1 of the ACP stack the maintainer approved: "rely on the agent's own sandboxes and permission models rather than implementing our own, like Claude and Codex".

Stacked on #13616 (Grok launches in the thread's permission mode), which this stack builds on; it will retarget to t3code/codex-turn-mapping when that lands.

What changed

  • AcpAdapterV2Flavor gets an optional clientFileSystem: { readTextFile, writeTextFile } that receives the session cwd. The adapter still runs the runtime-policy guard first, then calls the flavor's handler instead of the generic one.

  • AntigravityAdapterV2 moves its containment handlers there (roots: session cwd + attachments dir, unchanged) and no longer registers them on the runtime itself.

  • The handler gets the policy cwd as-is (string | null). A session without a workspace gets only the attachments dir as a root instead of falling back to the server's own process.cwd(). In production the runtime policy always carries the thread's worktree or project root (RuntimePolicy.ts layerFromProjectRepository fails without a project), so this only matters for callers that build a policy with cwd: null.

  • Containment resolves the final path, not only its directory. A symlink inside the workspace that points outside it is refused for reads and writes. A dangling link is refused rather than followed, so it can't create a file outside the workspace.

  • The allowed roots come from the policy active when the request arrives, the same one the runtime policy guard reads (clientPolicyContext), instead of the policy the session opened with. In today's orchestrator an ACP session serves one thread (supportsMultipleProviderThreadsPerSession: false), and a workspace change detaches the session (Orchestrator.ts "Workspace changed."). So this closes a footgun rather than a live escape: startTurn already switches latestRuntimePolicy to the incoming turn's policy.

Decisions (override if you disagree)

  • Antigravity keeps client fs ON with its own containment handler until someone probes whether its default mode still asks with a diff when fs is off (V1 on main opts it in for the same reason, AntigravityAcpSupport.ts). Later layers turn client fs off for everyone else.

Verification

  • vp test run src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts src/orchestration-v2/Adapters/AcpAdapterV2.test.ts src/provider/Drivers/AntigravityDriver.test.ts src/provider/acp/AntigravityAcpSupport.test.ts (apps/server): 165 passed.
  • Symlinks: the containment test now also covers a read and a write through an in-workspace symlink to an outside file (both denied, and the file is unchanged), a write through a dangling link (denied, nothing created), a new file under an in-workspace directory link to outside (denied), and a link that stays inside (served). Before the fix, the read through the link succeeded. Allowing dangling links makes the test fail.
  • Workspace: new test AntigravityAdapterV2 workspace changes > confines file requests to the workspace of the turn in progress. A session opened for workspace A runs a turn for workspace B: B's file is served and A's is denied. It fails if the handler uses the cwd the session opened with.
  • New test AntigravityAdapterV2 client file system > confines agent file requests to the workspace under full access opens a real Antigravity adapter session against the ACP mock agent (Antigravity profile) under full-access, then calls the fs handlers the runtime actually kept: a write inside the workspace and a read from the attachments dir succeed; a read and a write outside both fail and nothing is written. With the fix reverted the test fails (expected false to be true on the outside read).
  • vp exec tsc --noEmit -p . (apps/server): no errors or warnings. vp lint on the touched files: only the pre-existing unused NodePath import warning in AcpAdapterV2.ts.
  • Not run: live Antigravity (not installed on this machine), repo-wide checks.

Model: Claude Opus 5.5 (Claude Code)

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 25, 2026
allowedRoots: [cwd, options.serverConfig.attachmentsDir],
request,
}),
writeTextFile: (request, cwd) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Critical Adapters/AntigravityAdapterV2.ts:190

readTextFile and writeTextFile still follow a final symlink after the containment check, so an in-workspace symlink targeting outside cwd or attachmentsDir can disclose or overwrite arbitrary server files. Update readAntigravityClientTextFile and writeAntigravityClientTextFile to validate the canonicalized target path (or reject final-component symlinks), not only dirname(resolved).

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.ts around line 190:

`readTextFile` and `writeTextFile` still follow a final symlink after the containment check, so an in-workspace symlink targeting outside `cwd` or `attachmentsDir` can disclose or overwrite arbitrary server files. Update `readAntigravityClientTextFile` and `writeAntigravityClientTextFile` to validate the canonicalized target path (or reject final-component symlinks), not only `dirname(resolved)`.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 4.9 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.4 KiB — 29.3 KiB ✅
Codex Live turn messages — 2 — 8 ✅
Claude Total thread wire — 4.9 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 20.7 KiB — 29.3 KiB ✅
Claude Live turn messages — 1 — 8 ✅

Baseline: unavailable · PR result: bae8e22 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR changes production filesystem authorization and workspace containment for Antigravity, including symlink resolution and no-workspace behavior. An unresolved critical symlink-escape finding makes human verification necessary despite the apparent canonical-path fix in the current diff.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment thread apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts Outdated
@juliusmarminge
juliusmarminge force-pushed the v2/antigravity-containment branch from e5a3e33 to dcb5ce1 Compare September 25, 2026 09:40
@juliusmarminge
juliusmarminge changed the base branch from t3code/codex-turn-mapping to v2/grok-runtime-mode September 25, 2026 09:40
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Sep 25, 2026
@juliusmarminge
juliusmarminge changed the base branch from v2/grok-runtime-mode to t3code/codex-turn-mapping September 25, 2026 22:35
@juliusmarminge
juliusmarminge changed the base branch from t3code/codex-turn-mapping to v2/grok-runtime-mode September 25, 2026 22:35
Base automatically changed from v2/grok-runtime-mode to t3code/codex-turn-mapping September 25, 2026 22:48
juliusmarminge and others added 3 commits September 25, 2026 15:50
The generic ACP fs handlers registered after Antigravity's own and replaced
them (effect-acp keeps the last handler per method), so Antigravity could
read and write any path the T3 server can. The flavor now supplies its
handlers through `clientFileSystem`, and the adapter serves them behind the
runtime policy guard instead of the unconfined generic ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…f the server cwd

A session policy without a cwd made the server's own cwd Antigravity's
containment root. Pass the nullable policy cwd through and allow only the
attachments dir in that case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nt workspace

Two escapes from Antigravity's workspace containment:

- The check resolved only the parent directory, so a symlink inside the
  workspace pointing outside it could be read or overwritten through. The
  final target is now canonicalized before the check, and a link that
  exists but cannot be resolved (dangling) is refused rather than written
  through.
- The allowed roots came from the policy the session opened with. They now
  come from the policy active when the request arrives, the one the policy
  guard already checks, so a session carried into a turn for another
  workspace cannot reach the previous one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the v2/antigravity-containment branch from 0abd34b to bae8e22 Compare September 25, 2026 22:55
@juliusmarminge
juliusmarminge merged commit 1d1b3bc into t3code/codex-turn-mapping Sep 25, 2026
24 checks passed
@juliusmarminge
juliusmarminge deleted the v2/antigravity-containment branch September 25, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant