fix(desktop): stop home-directory repo scans when no roots are configured

An empty repo_scan_roots silently expanded to a bounded scan of the
user's entire home directory on every Desktop launch, with no way to
restrict the traversal short of disabling discovery outright. Empty
roots are now a safe no-op: users must explicitly configure
desktop.repo_scan_roots for filesystem scanning, and session-derived
projects remain available. The default config comment documents the
opt-in.

Fixes #53328

Co-authored-by: John Kim Querobines <jkim.querobines@gmail.com>
This commit is contained in:
Hermes Agent
2026-09-25 11:36:29 -05:00
committed by brooklyn!
parent 678a4762b8
commit 67c6fdc092
4 changed files with 34 additions and 5 deletions

View File

@@ -50,6 +50,13 @@ describe('scanGitRepos', () => {
expect(read).not.toHaveBeenCalled()
})
it('does not fall back to scanning the home directory when roots are empty (#53328)', async () => {
const read = vi.spyOn(fs.promises, 'readdir')
await expect(scanGitRepos([], { enabled: true })).resolves.toEqual([])
expect(read).not.toHaveBeenCalled()
})
it('scans only configured roots and excludes complete subtrees', async () => {
const root = tempDir()
const included = path.join(root, 'included')

View File

@@ -105,12 +105,14 @@ async function mapLimit<T>(items: T[], limit: number, fn: (item: T) => Promise<v
}
/**
* Scan roots for Git repositories. An empty root list preserves the historical
* home-directory scan. Disabled discovery returns before resolving home or
* reading the filesystem.
* Scan explicitly configured roots for Git repositories. An empty root list is
* intentionally a no-op: silently expanding it to the user's home directory
* traverses the whole home (and, on macOS, can reach TCC-protected locations)
* during ordinary Desktop startup (#53328). Disabled discovery returns before
* resolving home or reading the filesystem.
*/
export async function scanGitRepos(roots: string[], options: RepoScanOptions = {}) {
if (options.enabled === false) {
if (options.enabled === false || !Array.isArray(roots) || roots.length === 0) {
return []
}

View File

@@ -2569,7 +2569,9 @@ DEFAULT_CONFIG = {
# of the active theme's own sans stack so missing glyphs still fall through. Empty = the
# theme's face. The terminal pane is terminal.font_family.
"font_family": "",
# Git repo discovery for the Projects sidebar; empty roots = bounded scan of $HOME.
# Git repo discovery for the Projects sidebar. Empty roots are a safe
# no-op; users must explicitly configure roots for filesystem scanning.
# Session-derived projects remain available.
"repo_scan_enabled": True,
"repo_scan_roots": [],
"repo_scan_exclude_paths": [],

View File

@@ -0,0 +1,18 @@
from hermes_cli.config import DEFAULT_CONFIG
from hermes_cli.web_server import CONFIG_SCHEMA
def test_desktop_repo_discovery_defaults_are_opt_in_by_root():
desktop = DEFAULT_CONFIG["desktop"]
assert desktop["repo_scan_enabled"] is True
# Empty roots are deliberately safe: Desktop does not infer a home-wide
# search. Users must explicitly configure roots or use session projects.
assert desktop["repo_scan_roots"] == []
assert desktop["repo_scan_exclude_paths"] == []
def test_desktop_repo_discovery_keys_are_in_generated_schema():
assert CONFIG_SCHEMA["desktop.repo_scan_enabled"]["type"] == "boolean"
assert CONFIG_SCHEMA["desktop.repo_scan_roots"]["type"] == "list"
assert CONFIG_SCHEMA["desktop.repo_scan_exclude_paths"]["type"] == "list"