You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Once WorkspacePathResolver (#389) provides safe async path canonicalization, we need a policy layer that tools can call instead of performing raw fs operations after a boolean check. The current pattern — tool calls isPathOutsideWorkspace(), gets a boolean, then proceeds with its own fs read/write — is structurally easy to get wrong: the check and the operation are decoupled, so one missed call site (like ApplyDiffTool in PR #241) leaves a hole.
The goal of this layer is to make the boundary check structurally impossible to bypass: tools request an authorized operation and get back either a resolved path they are permitted to use, or a structured error they must surface.
Developer Note
Create src/core/workspace/WorkspaceFileAccess.ts (or equivalent location consistent with project conventions).
Compare resolved path against each workspace folder (also resolved via resolveRealPath). On error resolving a folder, skip it (cannot prove containment).
If outside all workspace folders and allowSymlinksOutsideWorkspace is false, return { ok: false, reason: … }.
If allowSymlinksOutsideWorkspace is true, compare lexical paths (no realpath) — restoring pre-fix behavior for users who opt in.
Expose authorizeWrite with the same signature for write operations. (Both may share an internal authorize helper.)
Read allowSymlinksOutsideWorkspace from task.providerRef.deref()?.getState() with a try/catch — provider may have been torn down. Default to false.
Part of #169. Sub-issue 2 of 4. Depends on #389.
Context
Once
WorkspacePathResolver(#389) provides safe async path canonicalization, we need a policy layer that tools can call instead of performing rawfsoperations after a boolean check. The current pattern — tool callsisPathOutsideWorkspace(), gets a boolean, then proceeds with its ownfsread/write — is structurally easy to get wrong: the check and the operation are decoupled, so one missed call site (likeApplyDiffToolin PR #241) leaves a hole.The goal of this layer is to make the boundary check structurally impossible to bypass: tools request an authorized operation and get back either a resolved path they are permitted to use, or a structured error they must surface.
Developer Note
Create
src/core/workspace/WorkspaceFileAccess.ts(or equivalent location consistent with project conventions).authorizeRead(options): Promise<AuthorizeResult>Options:
task: Task— for readingallowSymlinksOutsideWorkspacefrom provider state (defaultfalsewhen state is unavailable — fail closed).requestedPath: string— absolute path as supplied by the tool.source: string— tool name, for error messages.Internally:
resolveRealPathfrom feat(security): introduce WorkspacePathResolver — safe async symlink-aware path canonicalization #389. On error, return{ ok: false, reason: "realpath_failed" | "permission_denied", message }.resolveRealPath). On error resolving a folder, skip it (cannot prove containment).allowSymlinksOutsideWorkspaceisfalse, return{ ok: false, reason: … }.allowSymlinksOutsideWorkspaceistrue, compare lexical paths (no realpath) — restoring pre-fix behavior for users who opt in.Expose
authorizeWritewith the same signature for write operations. (Both may share an internalauthorizehelper.)Read
allowSymlinksOutsideWorkspacefromtask.providerRef.deref()?.getState()with a try/catch — provider may have been torn down. Default tofalse.Tests (
src/core/workspace/__tests__/WorkspaceFileAccess.spec.ts):ok: false, reason: "symlink_escapes_workspace".allowSymlinksOutsideWorkspace: true→ symlink inside workspace → outside:ok: true.EACCESon symlink target:ok: false, reason: "permission_denied".ok: true,resolvedPathmatches canonical path.ok: true(creation flows still work).undefined): defaults to fail-closed.Does not change any tool behavior. No UI, no i18n, no tool migrations in this PR.