Severity: Critical
`process_doc` (singular) does not apply symlink safety checks before reading a file. An adversary who can place a symlink inside the indexed plans directory can read any file on the filesystem accessible to the server process.
Root cause
`process_doc.ts` calls `validatePath()` which uses `path.resolve()` for containment checks. `path.resolve()` does not follow symlinks. However, `readFile()` in Node.js does follow symlinks, so the containment check is bypassed.
The sibling tools `process_docs` (plural), `create_doc`, and `update_doc` all correctly call `checkPathSafety()` and `checkSymlinkAncestors()` from `src/utils/fs-safety.ts`. `process_doc` (singular) calls neither.
Fix
In `src/tools/process-doc.ts`, add the same safety checks as `process_docs`:
```typescript
import { checkPathSafety, checkSymlinkAncestors } from '../utils/fs-safety.js';
// After validatePath() and existsSync() checks, before readFile():
const safetyCheck = checkPathSafety(validated.absolute, {});
if (!safetyCheck.safe) {
return { content: [{ type: 'text', text: `Permission denied: ${safetyCheck.reason}` }], isError: true };
}
const ancestorCheck = checkSymlinkAncestors(validated.absolute, docsRoot);
if (!ancestorCheck.safe) {
return { content: [{ type: 'text', text: `Permission denied: ${ancestorCheck.reason}` }], isError: true };
}
```
Files to modify
- `src/tools/process-doc.ts` — add `checkPathSafety` + `checkSymlinkAncestors` calls
- `tests/tools/process-doc.test.ts` — add symlink traversal rejection test
Also audit
- `src/tools/manage-tags.ts` — reads files after `validatePath` without symlink checks (medium risk)
- `src/tools/delete-doc.ts` — lower risk (`unlink` removes the symlink itself, not the target)
Severity: Critical
`process_doc` (singular) does not apply symlink safety checks before reading a file. An adversary who can place a symlink inside the indexed plans directory can read any file on the filesystem accessible to the server process.
Root cause
`process_doc.ts` calls `validatePath()` which uses `path.resolve()` for containment checks. `path.resolve()` does not follow symlinks. However, `readFile()` in Node.js does follow symlinks, so the containment check is bypassed.
The sibling tools `process_docs` (plural), `create_doc`, and `update_doc` all correctly call `checkPathSafety()` and `checkSymlinkAncestors()` from `src/utils/fs-safety.ts`. `process_doc` (singular) calls neither.
Fix
In `src/tools/process-doc.ts`, add the same safety checks as `process_docs`:
```typescript
import { checkPathSafety, checkSymlinkAncestors } from '../utils/fs-safety.js';
// After validatePath() and existsSync() checks, before readFile():
const safetyCheck = checkPathSafety(validated.absolute, {});
if (!safetyCheck.safe) {
return { content: [{ type: 'text', text: `Permission denied: ${safetyCheck.reason}` }], isError: true };
}
const ancestorCheck = checkSymlinkAncestors(validated.absolute, docsRoot);
if (!ancestorCheck.safe) {
return { content: [{ type: 'text', text: `Permission denied: ${ancestorCheck.reason}` }], isError: true };
}
```
Files to modify
Also audit