fix: coord review remediations — path traversal, JSON parse, race condition
Addresses code review findings from P2-005:
- Validate projectPath against allowed workspace roots (path traversal)
- Guard JSON.parse with try/catch in loadMission, readActiveSession, readSessionLock
- Add delay after stale lock removal to reduce race window
- Add @Inject(CoordService) per project guideline (no emitDecoratorMetadata)
- Eliminate double loadMission in getTaskStatus via shared buildStatusSummary
- Fix fragile prompt-inclusion check to test original command for {prompt}
- Add mkdir to writeAtomic for consistency with other atomic helpers
Closes #80
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,39 @@
|
||||
import { Controller, Get, NotFoundException, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Controller,
|
||||
Get,
|
||||
Inject,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import path from 'node:path';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CoordService } from './coord.service.js';
|
||||
|
||||
/** Only paths under these roots are allowed for coord queries. */
|
||||
const ALLOWED_ROOTS = [process.cwd()];
|
||||
|
||||
function resolveAndValidatePath(raw: string | undefined): string {
|
||||
const resolved = path.resolve(raw ?? process.cwd());
|
||||
const isAllowed = ALLOWED_ROOTS.some(
|
||||
(root) => resolved === root || resolved.startsWith(`${root}/`),
|
||||
);
|
||||
if (!isAllowed) {
|
||||
throw new BadRequestException('projectPath is outside the allowed workspace');
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@Controller('api/coord')
|
||||
@UseGuards(AuthGuard)
|
||||
export class CoordController {
|
||||
constructor(private readonly coordService: CoordService) {}
|
||||
constructor(@Inject(CoordService) private readonly coordService: CoordService) {}
|
||||
|
||||
@Get('status')
|
||||
async missionStatus(@Query('projectPath') projectPath?: string) {
|
||||
const resolvedPath = projectPath ?? process.cwd();
|
||||
const resolvedPath = resolveAndValidatePath(projectPath);
|
||||
const status = await this.coordService.getMissionStatus(resolvedPath);
|
||||
if (!status) throw new NotFoundException('No active coord mission found');
|
||||
return status;
|
||||
@@ -17,13 +41,13 @@ export class CoordController {
|
||||
|
||||
@Get('tasks')
|
||||
async listTasks(@Query('projectPath') projectPath?: string) {
|
||||
const resolvedPath = projectPath ?? process.cwd();
|
||||
const resolvedPath = resolveAndValidatePath(projectPath);
|
||||
return this.coordService.listTasks(resolvedPath);
|
||||
}
|
||||
|
||||
@Get('tasks/:taskId')
|
||||
async taskStatus(@Param('taskId') taskId: string, @Query('projectPath') projectPath?: string) {
|
||||
const resolvedPath = projectPath ?? process.cwd();
|
||||
const resolvedPath = resolveAndValidatePath(projectPath);
|
||||
const detail = await this.coordService.getTaskStatus(resolvedPath, taskId);
|
||||
if (!detail) throw new NotFoundException(`Task ${taskId} not found in coord mission`);
|
||||
return detail;
|
||||
|
||||
Reference in New Issue
Block a user