Skip to content

feat(security): introduce WorkspaceFileAccess — central workspace boundary authorization (#390)#436

Draft
proyectoauraorg wants to merge 2 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/390-workspace-file-access
Draft

feat(security): introduce WorkspaceFileAccess — central workspace boundary authorization (#390)#436
proyectoauraorg wants to merge 2 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/390-workspace-file-access

Conversation

@proyectoauraorg
Copy link
Copy Markdown
Contributor

Part of #169. Sub-issue 2 of 4. Depends on #428 (#389 WorkspacePathResolver).

Note

Stacked on #428. This branch is built on top of feat/389-workspace-path-resolver, so the diff currently includes the WorkspacePathResolver commit. Once #428 merges to main, I'll rebase and that commit drops out, leaving only the WorkspaceFileAccess change. Draft until #428 lands.

What

Adds src/core/workspace/WorkspaceFileAccess.ts — the policy layer #390 calls for. Tools request an authorized operation instead of doing a raw isPathOutsideWorkspace() boolean check followed by their own fs access. The decoupled check-then-act pattern is structurally easy to get wrong; the missed ApplyDiffTool check in #241 is exactly the failure mode this prevents.

type AuthorizeResult =
  | { ok: true; resolvedPath: string }
  | { ok: false; reason: "outside_workspace" | "symlink_escapes_workspace" | "realpath_failed" | "permission_denied"; message: string }

authorizeRead(options): Promise<AuthorizeResult>
authorizeWrite(options): Promise<AuthorizeResult>

The allowSymlinksOutsideWorkspace setting is introduced here (its home per the issue) in GlobalSettings + ExtensionState.

Scope

Per the issue: does not change any tool behavior. No UI, no i18n, no tool migrations — those are #391/#392.

Tests

src/core/workspace/__tests__/WorkspaceFileAccess.spec.ts — real temp dirs + real symlinks (no fs mocking), covering all six scenarios from the issue plus plainly-outside, provider-throws, and no-workspace-open. Skips symlink/EACCES cases cleanly on Windows/root.

  • tsc --noEmit clean
  • eslint --max-warnings=0 clean (src + types)
  • ✅ 9/9 tests pass

… path canonicalization (Zoo-Code-Org#389)

First of four sub-issues under Zoo-Code-Org#169. Adds `src/utils/WorkspacePathResolver.ts` with a single
async `resolveRealPath(target)` that canonicalizes a path by following symlinks via
`fs.promises.realpath`:

- Walks up to the nearest existing ancestor on ENOENT and re-appends the trailing segments,
  so not-yet-created files under a symlinked ancestor still resolve correctly.
- Re-throws non-ENOENT errors (EACCES, ELOOP) so callers can fail closed rather than fall back
  to a lexical path.
- Case-normalizes the result on macOS/Windows for reliable comparison against uri.fsPath.

Pure utility — no workspace policy, settings, or tool changes (those land in WorkspaceFileAccess
(Zoo-Code-Org#390) and the migrations in Zoo-Code-Org#391/Zoo-Code-Org#392). Covered by integration tests using real symlinks in a
temp directory.
…ndary authorization (Zoo-Code-Org#390)

Sub-issue 2 of Zoo-Code-Org#169, building on WorkspacePathResolver (Zoo-Code-Org#389/Zoo-Code-Org#428).

Adds src/core/workspace/WorkspaceFileAccess.ts. `authorizeRead`/`authorizeWrite`
return either `{ ok: true, resolvedPath }` or a structured
`{ ok: false, reason }` (outside_workspace | symlink_escapes_workspace |
realpath_failed | permission_denied), so tools can no longer perform a boolean
check and then act on a stale path — the missed `ApplyDiffTool` check in Zoo-Code-Org#241 is
exactly the failure mode this prevents.

Default behavior canonicalizes the requested path and every workspace folder via
`resolveRealPath` and fails closed; the `allowSymlinksOutsideWorkspace` opt-in
restores the pre-Zoo-Code-Org#169 lexical behavior. The setting is introduced here (its home)
in GlobalSettings and ExtensionState. No tool migrations, UI, or i18n in this PR.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c233276-e05c-4f10-ac4c-f960e0886216

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/workspace/WorkspaceFileAccess.ts 79.78% 19 Missing ⚠️
src/utils/WorkspacePathResolver.ts 93.33% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant