security(gateway): validate transcript sessionFile path

Problem:
`resolveTranscriptPath()` returned user-provided `sessionFile` directly
without any validation. An attacker could supply arbitrary paths like
`/etc/cron.d/evil` to write outside the intended directory.

Solution:
- When storePath exists: validate sessionFile stays within store directory
- When no storePath: require absolute path (reject relative paths without context)
- Use path.relative() to detect escape attempts

Fixes #3277 (partial)
[AI-assisted: lightly tested, code understood]
This commit is contained in:
Robby 2026-01-28 10:51:16 +00:00
parent 9688454a30
commit 00a91da6eb

View File

@ -56,11 +56,24 @@ function resolveTranscriptPath(params: {
sessionFile?: string;
}): string | null {
const { sessionId, storePath, sessionFile } = params;
if (sessionFile) return sessionFile;
if (sessionFile) {
if (storePath) {
const storeDir = path.dirname(storePath);
const absSessionFile = path.resolve(storeDir, sessionFile);
const rel = path.relative(storeDir, absSessionFile);
if (rel.startsWith("..") || path.isAbsolute(rel)) {
throw new Error("sessionFile escapes store directory");
}
return absSessionFile;
}
if (!path.isAbsolute(sessionFile)) {
throw new Error("sessionFile must be absolute when storePath is not set");
}
return sessionFile;
}
if (!storePath) return null;
return path.join(path.dirname(storePath), `${sessionId}.jsonl`);
}
function ensureTranscriptFile(params: { transcriptPath: string; sessionId: string }): {
ok: boolean;
error?: string;