fix(installer,cli): refuse to index $HOME / filesystem root (#860)
Running the installer or `codegraph init`/`index` from $HOME auto-indexed the entire home tree (installer indexes process.cwd() with no guard), producing a multi-GB ~/.codegraph/codegraph.db; the install dir sharing the ~/.codegraph name then made every home subdir resolve its root to $HOME. On pre-1.0 macOS the per-file watcher over that tree exhausted kern.maxfiles and crashed the machine (#845; the fd blowup was fixed in 1.0.0, this fixes the root cause). Add unsafeIndexRootReason() and refuse the home dir, a parent of home, and filesystem roots at the installer auto-index, `init`, and `index`. Overridable with --force. Closes #845.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { unsafeIndexRootReason } from '../src/directory';
|
||||
|
||||
/**
|
||||
* Guard for #845: the installer / `init` / `index` must refuse the home
|
||||
* directory and filesystem roots, which would otherwise index the entire tree
|
||||
* (multi-GB index, watcher churn, pre-1.0 macOS fd exhaustion that crashed the
|
||||
* machine). The classic trigger was running the installer from `$HOME`.
|
||||
*/
|
||||
describe('unsafeIndexRootReason', () => {
|
||||
const tmpDirs: string[] = [];
|
||||
afterEach(() => {
|
||||
for (const d of tmpDirs.splice(0)) {
|
||||
try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
|
||||
it('flags the home directory', () => {
|
||||
const reason = unsafeIndexRootReason(os.homedir());
|
||||
expect(reason).toBeTruthy();
|
||||
expect(reason).toContain('home');
|
||||
});
|
||||
|
||||
it('flags a parent of the home directory (broader than home)', () => {
|
||||
// dirname(home) is either a parent of home or — for a root-level home like
|
||||
// `/root` — the filesystem root; both are unsafe.
|
||||
expect(unsafeIndexRootReason(path.dirname(os.homedir()))).toBeTruthy();
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== 'win32')('flags the POSIX filesystem root', () => {
|
||||
expect(unsafeIndexRootReason('/')).toContain('filesystem root');
|
||||
});
|
||||
|
||||
it('allows a normal project directory', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-unsafe-'));
|
||||
tmpDirs.push(dir);
|
||||
expect(unsafeIndexRootReason(dir)).toBeNull();
|
||||
// …and a nested subdir of it.
|
||||
const nested = path.join(dir, 'packages', 'app');
|
||||
fs.mkdirSync(nested, { recursive: true });
|
||||
expect(unsafeIndexRootReason(nested)).toBeNull();
|
||||
});
|
||||
|
||||
it('matches the home directory case-insensitively on macOS/Windows', () => {
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') return;
|
||||
// The FS is case-insensitive there, so an upper-cased home path must still flag.
|
||||
expect(unsafeIndexRootReason(os.homedir().toUpperCase())).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user