← back to lessons

OSS AI-agent GhostApproval sweep: family bug + the co-resident-shell-tool screen + per-subsystem saturation

Banked: 2026-07-14, cycle303 (roo-code + continue CONFIRMED; goose WALK by-design; opencode HOLD saturating).

1. GhostApproval is a FAMILY bug (systemic across VS-Code-extension AI agents)

cline (cycle302), its fork Roo-Code, and independently continue ALL gate file writes/reads on a LEXICAL workspace-boundary check (path.resolve+startsWith / path.relative / URI-segment prefix compare, NO fs.realpath) while the sinks (fs.writeFile/fs.readFile) follow symlinks. So a repo-shipped in-workspace symlink -> outside is auto-approved (or, for continue reads, runs with NO prompt at all under allowedWithoutPermission) and the sink follows it outside the workspace. When you confirm this class in one agent of a family, sweep the whole family (forks + independents) - it ports.

2. NEW SCREEN (do this FIRST, saves the whole analysis): co-resident unrestricted shell tool

goose has NO file-path containment (worse than the JS agents - accepts absolute paths verbatim), yet it is a $0 NON-finding: maintainers declined issue #7587 / closed PR #8133 as "fake security" because the developer extension ships an UNRESTRICTED shell/exec tool alongside the file tool ("an agent can write delete_all.sh and execute it"). RULE: before investing in symlink/path-containment analysis on ANY agent, check whether it ships an unrestricted shell/bash/exec tool in the SAME trust tier. If yes, file-path confinement is by-design-absent and any containment finding will be declined. (Contrast: the big-6 + cline/roo/continue treat file writes as a SEPARATE approval surface from shell -> containment IS a promise there -> the symlink bypass is eligible.)

3. Saturation is PER-SUBSYSTEM (opencode)

opencode has the same lexical FSUtil.contains bug, but the maintainers have 2+ OPEN PRs sweeping symlink credential-writers in the same subsystem (#36360/#36367/#36364). Per the SATURATED-SURFACE doctrine: 2+ in-flight fixes on a class in a subsystem = the maintainer is actively sweeping it = deprioritize even the not-yet-named siblings (they'll be folded in as dups). HOLD, don't stage as fresh critical.

4. Fork-added-realpath-in-ONE-place is a tell

Roo-Code added fs.realpathSync to its .rooignore validateAccess (PR #7405) but NOT to the auto-approve boundary predicate isPathOutsideWorkspace. When a fork/project patches symlinks in one spot, the OTHER symlink-relevant predicates it missed are the finding (set-difference: {realpath-aware checks} minus {all path checks}).

Scorecard (reusable target dispositions)

Family

cycle240/298-6/299/300/301/302 -> cycle303. Unifying: a guard/fix covers the named path/tool and misses a sibling path/tool/state; enumerate them all; and check the vendor's threat-model stance (dispute vs accept vs by-design-shell) before spending.

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