fix: code cleanup — path traversal, test leaks, dead code, consistency

- Add path traversal protection to GET /api/cases/:name and fix-plan
- Use safePathSchema for LinkCaseSchema.path
- Fix QR auth test timer leak (afterAll → afterEach) and env var try/finally
- Remove dead terminal size check after Zod validation in resize route
- Remove no-op sampleCount guard in adaptive timing
- Replace hardcoded values with constants in notification-manager and subagent-windows
- Add Zod validation to POST /api/auth/revoke
- Use _apiPut instead of raw fetch in subagent-windows
- Add SwipeHandler.cleanup() for consistency with other mobile handlers
- Move NiceConfig/ProcessStats from types/plan.ts to types/common.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-03-01 17:26:31 +01:00
co-authored by Claude Opus 4.6
parent f94ab08cbe
commit d094d9ec50
11 changed files with 94 additions and 71 deletions
+16
View File
@@ -162,6 +162,14 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
app.get('/api/cases/:name', async (req) => {
const { name } = req.params as { name: string };
// Security: Path traversal protection
const resolvedPath = resolve(join(CASES_DIR, name));
const resolvedBase = resolve(CASES_DIR);
const relPath = relative(resolvedBase, resolvedPath);
if (relPath.startsWith('..') || isAbsolute(relPath)) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case name');
}
// First check linked cases
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try {
@@ -197,6 +205,14 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
app.get('/api/cases/:name/fix-plan', async (req) => {
const { name } = req.params as { name: string };
// Security: Path traversal protection
const resolvedPath = resolve(join(CASES_DIR, name));
const resolvedBase = resolve(CASES_DIR);
const relPath = relative(resolvedBase, resolvedPath);
if (relPath.startsWith('..') || isAbsolute(relPath)) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case name');
}
// Get case path (check linked cases first, then CASES_DIR)
let casePath: string | null = null;
-10
View File
@@ -43,8 +43,6 @@ import { RunSummaryTracker } from '../../run-summary.js';
import {
MAX_INPUT_LENGTH,
MAX_TERMINAL_COLS,
MAX_TERMINAL_ROWS,
MAX_SESSION_NAME_LENGTH,
} from '../../config/terminal-limits.js';
@@ -495,14 +493,6 @@ export function registerSessionRoutes(
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found');
}
// Note: Zod already validates that cols and rows are positive integers within bounds
if (cols > MAX_TERMINAL_COLS || rows > MAX_TERMINAL_ROWS) {
return createErrorResponse(
ApiErrorCode.INVALID_INPUT,
`Terminal dimensions exceed maximum (${MAX_TERMINAL_COLS}x${MAX_TERMINAL_ROWS})`
);
}
session.resize(cols, rows);
return { success: true };
});
+4 -3
View File
@@ -19,6 +19,7 @@ import {
CpuLimitSchema,
SubagentWindowStatesSchema,
SubagentParentMapSchema,
RevokeSessionSchema,
} from '../schemas.js';
import { subagentWatcher } from '../../subagent-watcher.js';
import { imageWatcher } from '../../image-watcher.js';
@@ -194,9 +195,9 @@ export function registerSystemRoutes(
// ========== Auth Session Revocation ==========
app.post('/api/auth/revoke', async (req) => {
const body = req.body as { sessionToken?: string } | undefined;
if (body?.sessionToken) {
ctx.authSessions?.delete(body.sessionToken);
const result = RevokeSessionSchema.safeParse(req.body);
if (result.success && result.data.sessionToken) {
ctx.authSessions?.delete(result.data.sessionToken);
} else {
// Revoke all sessions (nuclear option)
ctx.authSessions?.clear();