Skip to content

fix(isolation): enforce copy path containment - #947

Merged
tomdps merged 7 commits into
the-open-engine:mainfrom
shixi-li:fix/copy-containment-boundary
Aug 12, 2026
Merged

fix(isolation): enforce copy path containment#947
tomdps merged 7 commits into
the-open-engine:mainfrom
shixi-li:fix/copy-containment-boundary

Conversation

@shixi-li

@shixi-li shixi-li commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add one pinned-root containment boundary for isolation copies
  • enforce it before directory creation, synchronous copies, and worker copies
  • preserve in-root symlink behavior while failing closed on traversal and source/destination escapes
  • propagate typed containment failures consistently from workers

Related issue

Closes #939

Symlink and scope policy

Source symlinks and existing destination symlinks remain allowed when their canonical targets stay inside the pinned source or destination root. Escaping source links, escaping or broken destination links, directory cycles, absolute paths, and traversal components are rejected. The supported producer remains local readdirSync(..., { withFileTypes: true }) traversal; archive or external member ingestion is out of scope.

The boundary pins root device/inode identity and revalidates canonical paths immediately before each filesystem effect. Node's path-based filesystem APIs cannot make that check/use sequence atomic against a same-user concurrent tree replacement, hard-link alias, or mount alias. Worker failure cancellation is also non-transactional: already completed safe in-root copies are not rolled back.

Testing

  • 22 dedicated containment regressions pass on the exact head
  • 64 related isolation and Docker-boundary tests pass
  • simulated win32 separator and POSIX compatibility regressions pass
  • npm run typecheck passes
  • full npm run lint passes with no errors (existing warnings remain)
  • changed files pass Prettier and ESLint with no errors
  • 10,000-file local scale check passes in 1.104 seconds on Node 24

Broader local validation note: the default parallel npm test run exhausted this host's process-spawn capacity. A corrected single-job retry progressed until the unchanged tests/unit/attach-stdin.test.js failed to start its PTY with posix_spawnp failed; the isolated test fails at the same spawn boundary before reaching changed code. The focused, related, static, and scale gates above pass.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR introduces a shared pinned-root boundary for isolation copies and applies it across traversal, directory creation, synchronous copies, and worker copies.

  • Canonicalizes and pins source and destination roots by device and inode.
  • Rejects traversal, escaping symlinks, broken destination links, and directory cycles.
  • Propagates typed containment failures from worker threads.
  • Adds focused synchronous and worker-path containment regressions.
  • Updates both agent guidance files with the validation-before-effect invariant.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains, and both previously reported documentation omissions are fixed at the current head.

Important Files Changed

Filename Overview
src/copy-containment.ts Adds centralized relative-path validation, root identity pinning, canonical containment checks, and typed worker-error reconstruction.
src/copy-worker.ts Routes worker copies through the shared boundary and reports the first unexpected or containment failure to the parent.
src/isolation-manager.js Applies containment checks throughout collection, directory creation, synchronous copying, and worker orchestration.
tests/isolation-copy-containment.test.js Covers traversal, symlink escapes and cycles, root replacement, in-root symlinks, and worker failure propagation.
CLAUDE.md Records the shared pinned-root and immediate revalidation invariant, resolving the previous Claude-guidance finding.
AGENTS.md Directs automated contributors to preserve the containment boundary across every isolation-copy phase, resolving the previous agent-guidance finding.

Sequence Diagram

sequenceDiagram
  participant IM as IsolationManager
  participant CB as CopyBoundary
  participant FS as Filesystem
  participant W as Copy Worker
  IM->>CB: Pin source and destination roots
  CB->>FS: Resolve canonical paths and identities
  IM->>CB: Validate traversal entries
  CB->>FS: Revalidate roots and resolve symlinks
  IM->>FS: Create validated destination directories
  alt Small copy
    IM->>CB: Resolve source and destination
    IM->>FS: copyFileSync
  else Worker copy
    IM->>W: Send files and expected boundary
    W->>CB: Re-pin and compare expected roots
    W->>CB: Resolve each source and destination
    W->>FS: copyFileSync
    W-->>IM: Result or typed containment error
  end
Loading

Reviews (6): Last reviewed commit: "refactor(isolation): share worker error ..." | Re-trigger Greptile

Comment thread src/copy-containment.js Outdated
Comment thread src/copy-containment.js Outdated
@shixi-li
shixi-li force-pushed the fix/copy-containment-boundary branch from a13645f to d709225 Compare August 9, 2026 09:43

shixi-li commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Updated the branch and rebased it onto current main (94ef674f).

  • removed the Opcore CLONE_DUPLICATE by replacing the copied try/catch test pattern with an assert.rejects predicate
  • documented the shared pinned-root / validation-before-effect invariant in both CLAUDE.md and AGENTS.md
  • replied to and resolved both Greptile threads

Local validation on head d709225d:

  • 22 passingtests/isolation-copy-containment.test.js
  • npm run typecheck
  • npm run lint (0 errors; existing warnings only)
  • Prettier check for all changed files
  • npm run opcore:check -- --base 94ef674f43cb03e60522e5b348f5241a3dcaabe3

The new CI run is now starting. Once it settles, could a maintainer please take a human review?

shixi-li commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up: the only underlying failure is invoke runs Pi in the requested worktree cwd and normalizes streamed JSONL, where its 300 ms provider timeout produced exitCode: null (295/296 provider tests passed). This PR does not touch provider code or that test; CI on the same upstream main SHA (94ef674f) is green, and npm run check:agent-cli-provider:ci passes locally. Opcore and the other executed gates passed.

Both my CLI rerun and the connected GitHub app were denied upstream Actions write permission. @mkceichelbeck, could you please rerun the failed jobs for run 31306614628 and take a human review when convenient?

@shixi-li
shixi-li force-pushed the fix/copy-containment-boundary branch from 20f230d to 97e3cf0 Compare August 12, 2026 02:46
@shixi-li

Copy link
Copy Markdown
Contributor Author

Rebased this PR onto current main (c245e2f0) and resolved the copy-runtime migration conflict without restoring the removed JavaScript worker: the containment boundary now lives in src/copy-containment.ts and is built/packaged with the TypeScript worker. The follow-up at a15df4a also shares worker error inspection so the introduced-change clone gate passes.

Validated on the current head:

  • npm run typecheck and the legacy runtime/lib builds
  • 22 passing in tests/isolation-copy-containment.test.js
  • package smoke test
  • Prettier and ESLint (0 errors; repository warnings only)
  • Opcore introduced-change validation, including clone.duplication

The branch is mergeable again and fresh CI is running.

@shixi-li
shixi-li force-pushed the fix/copy-containment-boundary branch from a15df4a to 52f3130 Compare August 12, 2026 06:49

@tomdps tomdps left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed against #939 and current main. The shared pinned-root boundary is enforced across traversal, directory creation, synchronous copies, and worker copies; containment violations fail closed while ordinary unreadable/broken entries retain existing skip behavior. Source/destination symlink escapes, cycles, raw traversal, root replacement, worker error propagation, and in-root compatibility are covered. I also validated a conflict-free synthetic merge with current main: 22 focused containment tests, 12 existing isolation-manager integration tests, full TypeScript build/typecheck, and package smoke all pass. The documented same-user path-based TOCTOU/hard-link/mount-alias limitation is acceptable for this defense-in-depth scope.

@tomdps
tomdps added this pull request to the merge queue Aug 12, 2026
Merged via the queue into the-open-engine:main with commit 4d07675 Aug 12, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.39.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(copy): enforce containment across every isolation copy sink

2 participants