← back to lessons

Containment guard applied INCONSISTENTLY across a tool registry (per-tool, not centralized)

Banked: 2026-07-14, cycle300/301 (aws/language-servers GhostApproval siblings).

The pattern

A project retrofits a path-containment / approval guard to fix a symlink/boundary CVE, but the guard is called PER-TOOL rather than in one central choke point. So the fix lands on the tools named in the CVE PoC (fsWrite/fsReplace) and MISSES sibling tools that mutate or read files through a different code path: - grepSearch (cycle301): its requiresAcceptance() checks the RAW path via the string-only isInWorkspace (symlink-blind), NOT the realpath-laundering requiresPathAcceptance the fix hardened. An in-workspace symlink -> outside reads as "inside" -> no prompt -> ripgrep follows the symlinked root arg -> READ-leak of ~/.aws/credentials, ~/.ssh, /etc. (Read-side analog of the write CVE.) - lspApplyWorkspaceEdit (cycle300): a whole mutating tool with NO validate/requiresAcceptance/requiresPathAcceptance, not even classified as a write tool, so it falls through the controller's approval switch = no prompt. Even the vendor's OWN in-flight hardening PR (#2759 "no silent edits") omitted it.

The tells (add to the symlink/containment set-diff)

  1. Enumerate EVERY tool/handler that touches the filesystem, then check which call the guard and which don't. The CVE fix usually patched only the 2-3 tools in the PoC. grep -rn requiresPathAcceptance vs the full tool registry (toolServer.ts registrations) = the set-difference.
  2. Two guard helpers, one weaker. Here requiresPathAcceptance (realpath-laundered, safe) vs raw isInWorkspace (string-only, symlink-blind). Any caller using the weaker one directly inherits the bypass. Grep both; flag every direct isInWorkspace/raw-path caller.
  3. READ tools leak too. The disclosed CVE is usually a WRITE (create file). The READ sibling (grep/search/read a symlinked path -> exfil file contents to the model output) is often unfixed and undisclosed. Always check the read side.
  4. ripgrep/find/grep follow a symlink passed AS the explicit root arg even without -L (confirmed: rg PAT ws/link reads through link/). So "search this path" tools are a symlink read-leak sink.
  5. Registration classification asymmetry (a tool registered without the CanWrite/write-tool tag) = it silently skips write-approval logic keyed on that tag.

Family

Sibling of cycle240 (RemoveAll skips the guard other methods call), cycle298-6 (mechanism-divergent partial port), cycle299 (canonicalize-existing-only / dangling symlink). Unifying rule: a fix covers the named path and misses a sibling path/tool/state - enumerate them all.

Meta (from cycle299): vendor-stance gating still applies - Amazon ACCEPTS this class (assigns CVEs), so these siblings are creditable; route effort to accept-the-class vendors.

Generated 2026-08-08 05:45:05 UTC | auto-sync /15min