Repository navigation
fix: never crawl the home directory for a file index - #306
Merged
Merged
Conversation
On macOS the desktop app asked for access to Downloads, Photos, Desktop and the Music library. The file-search index walks everything under its location, and two paths rooted that location in the home directory: the project picker's bare-word search runs against its `~` base, and requests naming no directory fall back to process.cwd(), which the sidecar inherited as `~` from the main process. The walk opens files inside every protected folder, and macOS prompts for each (confirmed in the TCC database; reproduced against a decoy home, where one directory-less find request read sentinels inside Downloads and the photos library bundle). Three changes close it off: - core: unindexableDirectory (home per Global.Path.home, or a filesystem root) routes the search service to a new shallowLayer — find answers from one readdir of the location itself, glob/grep keep their on-demand ripgrep behaviour (extracted into a shared ripgrepGlobGrep; ripgrepLayer is unchanged in behaviour). - app: the picker's bare-word search matches direct children via the existing file.list path when its base is home or a root, instead of a recursive find.files — same answers against older servers too. - desktop: chdir to an empty app-owned folder (userData/cwd) instead of homedir(), so the cwd fallback has nothing to crawl. Still not `/`, which is what the original chdir was avoiding for ripgrep. With the guard in place the decoy-home request returns home's direct children and no sentinel is touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
On macOS the desktop app asked for permission to Downloads, Photos, Desktop, and the Music library. The TCC database showed all four requested by
ai.codev.desktop— Downloads granted and Photos denied one second apart, the signature of a single recursive walk of~.Root cause: materializing the file-search service walks everything under its location (fff at creation; ripgrep as an eagerly forked scan), and two paths rooted that location in the home directory:
find.files({directory})against its base, which is~;process.cwd()(workspace-routing.ts), which the sidecar inherited as~from the main process's startupchdir(homedir()).Reproduced end to end against a decoy home (sentinel files with pinned atimes): one directory-less
find/filerequest made the server open files insideDownloads/and insidePhotos Library.photoslibrary/— exactly the accesses macOS answers with those prompts.What
Three changes, defense in depth:
filesystem/search.ts): newunindexableDirectory()— true for the home directory (Global.Path.home, honoringCODEV_TEST_HOME) and filesystem roots. The layer chooser routes such locations to a newshallowLayer:findanswers from a singlereadDirectoryEntriesof the location itself (direct children only — no protected folder is entered), with the same fuzzysort/empty-query/limit semantics as the indexed layers;glob/grepkeep their on-demand ripgrep behavior, extracted into a sharedripgrepGlobGrepeffect (ripgrepLayerspreads it — no behavior change there).directory-picker-domain.ts): the picker's bare-word branch matches direct children via the existingfile.list-basedmatch()when its base is home or a filesystem root, instead of the recursivefind.files— same protection against older/remote servers.main/index.ts): startupchdirgoes to an empty app-owned folder (userData/cwd, created aftersetPath("userData")) instead ofhomedir(), so directory-less fallbacks have nothing to crawl. Still not/, which the original chdir was avoiding for ripgrep's sake.Browsing into a protected folder yourself (drilling into Downloads in the picker) still prompts, once — that access is user-chosen and shallow.
Test plan
find/filerequest returns["Music/","Pictures/","Desktop/","Downloads/"], finds nothing nested, and every sentinel atime is untouched.bun run devboots, UI renders, and main + all helper/sidecar processes run with cwd…/ai.codev.desktop.dev/cwd(verified vialsof).unindexableDirectory(home viaCODEV_TEST_HOME, dot-segments, fs root),shallowLayerfind (children only, no recursion, type filter) and on-demand glob; picker bare-word-over-home and over-root staying shallow.test/server293 pass.bun typecheckin core, app, desktop.DIVERGENCES.md gains one row covering all three upstream-file edits; CHANGELOG.md gains an Unreleased Privacy entry.
🤖 Generated with Claude Code