mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
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:
1 parent
c9b79097b0
commit
8d33f53fd0
28 files changed
+856
-198
No files matched your search
+23
-12
@@ -81,6 +81,7 @@ import { imageWatcher } from '../image-watcher.js';
|
||||
import { workflowRunWatcher, summarizeRun } from '../workflow-run-watcher.js';
|
||||
import { attachmentRegistry, buildFileThumbnailRoute, registerExternalAttachment } from '../attachment-registry.js';
|
||||
import { folderAllowedForUser, folderGrants } from '../folder-listing.js';
|
||||
import { isOutsideWorktreeRootRefused, loadAttachmentGuardConfig } from '../config/attachment-guard.js';
|
||||
import { getCli, enabledClis, listClis } from '../config/cli-registry/registry.js';
|
||||
import { isCliEntryInstalled, probeStockCliAvailability } from '../utils/cli-installed-probes.js';
|
||||
import { readCustomModelHosts, resolveCustomModelContextLength } from '../custom-model-hosts.js';
|
||||
@@ -3028,23 +3029,33 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
/**
|
||||
* The rules a worktree OUTSIDE the session's folder must pass to be followed (#765 follow-up):
|
||||
* none in a Docker case workspace (the list is read only when a candidate needs it, and an
|
||||
* unreadable list follows nothing), and in multi-user mode only inside the owner's space or
|
||||
* `workingDir`. Undefined for a remote or Docker session, whose pane directory is not a path the
|
||||
* agent works in on this host (it has no `paneCwd` either, so this is belt and braces).
|
||||
*
|
||||
* - none in a Docker case workspace (the list is read only when a candidate needs it, and an
|
||||
* unreadable list follows nothing);
|
||||
* - none while attachment workspace confinement is on (`attachmentConfineToWorkspace` /
|
||||
* `CODEMAN_ATTACHMENT_CONFINE`), since an outside worktree is a second served root;
|
||||
* - none at, inside or around an attachment blocked tree (`attachmentBlockedPaths` /
|
||||
* `CODEMAN_ATTACHMENT_BLOCKED_PATHS`, plus the defaults), or at a sensitive path;
|
||||
* - in multi-user mode, only inside the owner's space or `workingDir`.
|
||||
*
|
||||
* Codeman judges these rules when the pane moves. The file routes judge the two guard rules
|
||||
* again on every request (`isOutsideWorktreeRootRefused()`), so a setting changed later applies
|
||||
* at once. The Docker and multi-user rules are judged only when the pane moves. Undefined for a remote or Docker session,
|
||||
* whose pane directory is not a path the agent works in on this host (it has no `paneCwd`
|
||||
* either, so this is belt and braces).
|
||||
*/
|
||||
private outsideWorktreeOptions(session: Session): OutsideWorktreeOptions | undefined {
|
||||
if (session.remote || session.docker) return undefined;
|
||||
return {
|
||||
dockerWorkspaces: defaultDockerWorkspaces,
|
||||
...(isMultiUserMode() && {
|
||||
allowRoot: async (root: string): Promise<boolean> => {
|
||||
const owner = session.owner ? await findUser(session.owner) : undefined;
|
||||
return folderAllowedForUser(root, owner ? { username: owner.username, role: owner.role } : undefined, {
|
||||
workingDir: session.workingDir,
|
||||
});
|
||||
},
|
||||
}),
|
||||
allowRoot: async (root: string): Promise<boolean> => {
|
||||
if (isOutsideWorktreeRootRefused(root, await loadAttachmentGuardConfig())) return false;
|
||||
if (!isMultiUserMode()) return true;
|
||||
const owner = session.owner ? await findUser(session.owner) : undefined;
|
||||
return folderAllowedForUser(root, owner ? { username: owner.username, role: owner.role } : undefined, {
|
||||
workingDir: session.workingDir,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user