fix(files): harden the outside-worktree root after review

Keep the 1.49.0 API meaning: liveWorktree and root=live stay inside the
session folder, and an outside worktree is published as outsideWorktree
and walked only with root=live-any.

Refuse a symlinked .git folder as the main worktree, sensitive files
inside a followed worktree, and any outside worktree while attachment
confinement is on or around a blocked tree. The file routes re-check
the guard settings on every request, and a throwing rule check refuses.
The panel keeps its Back to worktree button while pinned to the session
folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Michael GrundbergandClaude Opus 5.5 committed 2026-11-06 20:33:14 +01:00
1 parent c9b79097b0
commit 8d33f53fd0
28 files changed
+856 -198

No files matched your search

+20 -13
View File
@@ -934,24 +934,31 @@ export interface SessionState {
* from disk, so a restart starts blank. Absent for remote and docker sessions
* (their local pane runs the ssh / docker-exec client) and for direct-PTY or
* rediscovered sessions. NOT a confinement root: the file routes stay confined
* to `workingDir`, and a path resolved outside it goes through the attachment
* routes like any other out-of-workspace path.
* to `workingDir`, except for an absolute path inside an
* {@link outsideWorktree}. Any other path resolved outside `workingDir` goes
* through the attachment routes like any other out-of-workspace path.
*/
paneCwd?: string;
/**
* The linked git worktree that the pane is in NOW, when there is one. Derived from
* {@link paneCwd} by `live-worktree.ts`, so it is runtime-only and absent wherever `paneCwd` is.
*
* Usually it lies inside `workingDir` (Claude Code's `.claude/worktrees/<name>`), and then it is
* NOT a confinement root: the Files panel only starts its listing here, after `GET .../files`
* has checked that the real folder still lies inside `workingDir`, since a symlink can replace
* it without moving the pane.
*
* With `outside: true` it is a worktree of the session's repository outside `workingDir`, which
* the repository itself vouched for, and its `path` is a real path. The file routes then serve
* absolute paths inside it as a second root, while it still resolves to that same path.
* The linked git worktree inside `workingDir` that the pane is in NOW, when there is one:
* Claude Code's `.claude/worktrees/<name>`, or any other worktree of the repository under
* the session's folder. Derived from {@link paneCwd} by `live-worktree.ts`, so it is
* runtime-only and absent wherever `paneCwd` is, and always a folder strictly below
* `workingDir`: a worktree elsewhere is reported in {@link outsideWorktree}, never here. NOT a
* confinement root; the Files panel only starts its listing here, after `GET .../files` has
* checked that the real folder still lies inside `workingDir`, since a symlink can replace it
* without moving the pane.
*/
liveWorktree?: LiveWorktree;
/**
* The worktree of the session's repository outside `workingDir` that the pane is in NOW, when
* there is one: a linked worktree, or the repository's main worktree for a session started in a
* linked one. It carries `outside: true`, and its `path` is a real path that the repository
* itself vouched for. The file routes serve absolute paths inside it as a second root while it
* still resolves to that same path. Runtime-only, like {@link liveWorktree}; at most one of
* the two fields is set.
*/
outsideWorktree?: LiveWorktree;
/** Remote execution metadata, present when this session runs over SSH through local tmux */
remote?: SessionRemote;
/** Docker execution metadata, present when this session runs inside a container via local tmux + docker exec */