Part of #169. Sub-issue 4 of 4. Depends on #391.
Context
After ReadFileTool is migrated (#391), the remaining tools still use the old isPathOutsideWorkspace / resolveIsOutsideWorkspace pattern. These are lower urgency than read (write tools already gated by explicit user approval flows) but must be migrated to close #169 fully and to make WorkspaceFileAccess the only call site for boundary enforcement.
Tools to migrate in this PR:
Also: add the allowSymlinksOutsideWorkspace opt-in setting to the settings UI and i18n (deferred from earlier PRs). Wire the global setting through to WorkspaceFileAccess as the default when provider state is unavailable. This closes #246 as well.
Developer Note
For each tool, the migration is the same pattern established in #391:
- Replace
isPathOutsideWorkspace / resolveIsOutsideWorkspace with workspaceFileAccess.authorizeRead or authorizeWrite as appropriate.
- On
!access.ok, surface access.message as a tool error and return before any fs operation.
- Use
access.resolvedPath for the actual filesystem call.
- Hoist
getState() once per execute() invocation and pass the allowSymlinksOutsideWorkspace flag down — do not fire a separate getState() per file in loops.
ApplyDiffTool special note: this tool currently has zero boundary check. Add authorizeRead before the file read in the UpdateFile/DeleteFile hunk handler, and authorizeWrite before the file write. The read-before-check race (content read before boundary check fires) identified in review is resolved naturally by using access.resolvedPath — the authorization must succeed before any fs call.
ListFilesTool / SearchFilesTool: authorizeRead on the root directory is sufficient for the tool-level check. The ripgrep --follow flag causing enumeration of out-of-workspace symlink targets is a separate issue and out of scope here.
Settings UI (closes #246):
- Add
allowSymlinksOutsideWorkspace checkbox to ContextManagementSettings.tsx under the workspace boundary section.
- Add i18n keys to all locale files (English first, machine-translate others or leave as English fallback).
- Wire through
global-settings.ts and vscode-extension-host.ts.
Cleanup:
- Remove
resolveIsOutsideWorkspace from BaseTool.ts — no longer needed.
- Remove any remaining direct imports of
isPathOutsideWorkspace from tool files.
isPathOutsideWorkspace in pathUtils.ts can be deprecated or removed once no callers remain; keep it if WorkspaceFileAccess delegates to it internally.
Tests: Each migrated tool should have at least one test verifying that a symlink-pointing-outside path is blocked, using the real WorkspaceFileAccess (not a mock of the boundary check).
Part of #169. Sub-issue 4 of 4. Depends on #391.
Context
After
ReadFileToolis migrated (#391), the remaining tools still use the oldisPathOutsideWorkspace/resolveIsOutsideWorkspacepattern. These are lower urgency than read (write tools already gated by explicit user approval flows) but must be migrated to close #169 fully and to makeWorkspaceFileAccessthe only call site for boundary enforcement.Tools to migrate in this PR:
WriteToFileToolEditToolEditFileToolSearchReplaceToolApplyPatchToolApplyDiffTool← currently has no boundary check at all (missed in PR fix(security): follow symlinks in workspace boundary check (#169) #241); this migration closes that gapListFilesToolSearchFilesToolGenerateImageTool(both input image read and output write paths)Also: add the
allowSymlinksOutsideWorkspaceopt-in setting to the settings UI and i18n (deferred from earlier PRs). Wire the global setting through toWorkspaceFileAccessas the default when provider state is unavailable. This closes #246 as well.Developer Note
For each tool, the migration is the same pattern established in #391:
isPathOutsideWorkspace/resolveIsOutsideWorkspacewithworkspaceFileAccess.authorizeReadorauthorizeWriteas appropriate.!access.ok, surfaceaccess.messageas a tool error and return before anyfsoperation.access.resolvedPathfor the actual filesystem call.getState()once perexecute()invocation and pass theallowSymlinksOutsideWorkspaceflag down — do not fire a separategetState()per file in loops.ApplyDiffToolspecial note: this tool currently has zero boundary check. AddauthorizeReadbefore the file read in theUpdateFile/DeleteFilehunk handler, andauthorizeWritebefore the file write. The read-before-check race (content read before boundary check fires) identified in review is resolved naturally by usingaccess.resolvedPath— the authorization must succeed before anyfscall.ListFilesTool/SearchFilesTool:authorizeReadon the root directory is sufficient for the tool-level check. The ripgrep--followflag causing enumeration of out-of-workspace symlink targets is a separate issue and out of scope here.Settings UI (closes #246):
allowSymlinksOutsideWorkspacecheckbox toContextManagementSettings.tsxunder the workspace boundary section.global-settings.tsandvscode-extension-host.ts.Cleanup:
resolveIsOutsideWorkspacefromBaseTool.ts— no longer needed.isPathOutsideWorkspacefrom tool files.isPathOutsideWorkspaceinpathUtils.tscan be deprecated or removed once no callers remain; keep it ifWorkspaceFileAccessdelegates to it internally.Tests: Each migrated tool should have at least one test verifying that a symlink-pointing-outside path is blocked, using the real
WorkspaceFileAccess(not a mock of the boundary check).