security: path validation, ReDoS prevention, picomatch, PID-based file lock

- Add validateProjectPath() to reject sensitive system directories
- Add isPathWithinRoot/isPathWithinRootReal for symlink-aware path checks
- Replace hand-rolled glob-to-regex with picomatch to prevent ReDoS
- Add isSafeRegex() to reject custom patterns with nested quantifiers
- Replace FileLock with PID-tracking version that detects stale locks
- Add symlink detection in removeDirectory/listDirectoryContents
- Add subdirectory name validation in ensureSubdirectory
- Add atomicWriteFileSync and corrupted file backup in config-writer
- Add MCP input validation (validateString) for all tool handlers
- Fix CLAUDE.md section replacement to handle ### subsections correctly
This commit is contained in:
Martin Oehlert
2026-02-10 11:22:04 +01:00
parent 4825661e02
commit 399d78b938
11 changed files with 1072 additions and 357 deletions
+12 -7
View File
@@ -149,7 +149,9 @@ export class CodeGraph {
this.queries = queries;
this.config = config;
this.projectRoot = projectRoot;
this.fileLock = new FileLock(db.getPath());
this.fileLock = new FileLock(
path.join(projectRoot, '.codegraph', 'codegraph.lock')
);
this.orchestrator = new ExtractionOrchestrator(projectRoot, config, queries);
this.resolver = createResolver(projectRoot, queries);
this.graphManager = new GraphQueryManager(queries);
@@ -375,8 +377,9 @@ export class CodeGraph {
*/
async indexAll(options: IndexOptions = {}): Promise<IndexResult> {
return this.indexMutex.withLock(async () => {
const locked = await this.fileLock.acquire();
if (!locked) {
try {
this.fileLock.acquire();
} catch {
return { success: false, filesIndexed: 0, filesSkipped: 0, nodesCreated: 0, edgesCreated: 0, errors: [{ message: 'Could not acquire file lock - another process may be indexing', severity: 'error' as const }], durationMs: 0 };
}
try {
@@ -416,8 +419,9 @@ export class CodeGraph {
*/
async indexFiles(filePaths: string[]): Promise<IndexResult> {
return this.indexMutex.withLock(async () => {
const locked = await this.fileLock.acquire();
if (!locked) {
try {
this.fileLock.acquire();
} catch {
return { success: false, filesIndexed: 0, filesSkipped: 0, nodesCreated: 0, edgesCreated: 0, errors: [{ message: 'Could not acquire file lock - another process may be indexing', severity: 'error' as const }], durationMs: 0 };
}
try {
@@ -435,8 +439,9 @@ export class CodeGraph {
*/
async sync(options: IndexOptions = {}): Promise<SyncResult> {
return this.indexMutex.withLock(async () => {
const locked = await this.fileLock.acquire();
if (!locked) {
try {
this.fileLock.acquire();
} catch {
return { filesChecked: 0, filesAdded: 0, filesModified: 0, filesRemoved: 0, nodesUpdated: 0, durationMs: 0 };
}
try {