fix: address PR review feedback — path safety, SQL injection, gate scoping

- Resolve relative filePath against input.cwd before statSync; early-return on ENOENT
- Replace LIKE '%path%' with exact json_each equality to prevent false matches
- Sanitize and parameterize LIMIT to prevent NaN SQL errors
- Fix day-sorting to use earliest epoch in group, not first (specificity-sorted) item
- Use exact path equality in deduplicateObservations instead of substring includes
- Scope FileReadGate by session+cwd to prevent worktree collisions
- Refresh lastAccess TTL on active sessions; throttle prune to every 50 calls
- Type params as (string | number)[] instead of any[]
- Remove unused permissionDecision fields from HookResult

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-06 17:29:59 -07:00
parent a60f79c44d
commit 31910fb265
6 changed files with 175 additions and 142 deletions
@@ -127,7 +127,8 @@ export class DataRoutes extends BaseRouteHandler {
const projectsParam = req.query.projects as string | undefined;
const projects = projectsParam ? projectsParam.split(',').filter(Boolean) : undefined;
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
const parsedLimit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
const limit = Number.isFinite(parsedLimit) && parsedLimit! > 0 ? parsedLimit : undefined;
const db = this.dbManager.getSessionStore().db;
const observations = getObservationsByFilePath(db, filePath, { projects, limit });
@@ -508,12 +509,12 @@ export class DataRoutes extends BaseRouteHandler {
* Returns: { firstAttempt: boolean }
*/
private handleFileContextGate = this.wrapHandler((req: Request, res: Response): void => {
const { sessionId, filePath } = req.body;
const { sessionId, filePath, cwd } = req.body;
if (!sessionId || !filePath) {
this.badRequest(res, 'sessionId and filePath are required');
return;
}
const firstAttempt = checkAndMark(sessionId, filePath);
const firstAttempt = checkAndMark(sessionId, filePath, cwd);
res.json({ firstAttempt });
});
}