fix(sessions): keep session store live during rotation#71359
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟡 Symlink/TOCTOU risk when creating session store rotation backups via copyFile()
DescriptionThe session store rotation logic creates backups using If an attacker can influence
Vulnerable code: const backupPath = `${storePath}.bak.${Date.now()}`;
await fs.promises.copyFile(storePath, backupPath);RecommendationHarden backup creation against symlink/hardlink attacks and TOCTOU:
Example approach (POSIX-oriented): import fs from "node:fs";
import path from "node:path";
const srcStat = await fs.promises.lstat(storePath);
if (!srcStat.isFile()) throw new Error("session store is not a regular file");
const backupPath = `${storePath}.bak.${Date.now()}`;
// Open destination with O_EXCL so an attacker cannot precreate a symlink/hardlink.
const dstFd = await fs.promises.open(backupPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
try {
// Copy from an already-opened source fd to avoid TOCTOU swaps.
const srcFd = await fs.promises.open(storePath, fs.constants.O_RDONLY);
try {
await fs.promises.copyFile(srcFd.fd, dstFd.fd);
} finally {
await srcFd.close();
}
} finally {
await dstFd.close();
}Also consider best-effort cleanup using 2. 🔵 Verbose logging of filesystem error object may disclose internal paths
Description
Vulnerable code: } catch (err) {
log.warn("session store rotation backup failed", { err });
return false;
}RecommendationAvoid logging the raw error object. Log a minimized/sanitized subset (e.g., Example fix: } catch (err) {
const e = err as NodeJS.ErrnoException;
log.warn("session store rotation backup failed", {
code: e.code,
syscall: e.syscall,
message: e.message, // consider redacting/normalizing if it may contain paths
path: e.path ? path.basename(String(e.path)) : undefined,
});
return false;
}If you need stack traces, gate them behind an explicit debug/verbose flag and ensure log redaction is applied before serialization. Analyzed PR: #71359 at commit Last updated on: 2026-04-25T02:30:16Z |
PR SummaryMedium Risk Overview Updates logging/error handling around rotation backup creation and adds unit tests covering the interrupted-rotation scenario and ensuring a legitimately empty live store is not replaced by stale Reviewed by Cursor Bugbot for commit 15de2b7. Bugbot is set up for automated code reviews on this repo. Configure here. |
2d2fdab to
122f130
Compare
Greptile SummaryThis PR fixes a crash-window bug in session store rotation by replacing Confidence Score: 5/5Safe to merge — the change is minimal, well-tested, and correctly eliminates the crash window without introducing new failure modes. The fix is a focused one-liner swap (rename → copyFile) with no semantic side effects. The return value is already discarded by the caller, so the change in behavior on copy failure (still returning false, still non-blocking) is consistent. Two clear regression tests cover both the interrupted-write and stale-backup scenarios. No security concerns. No files require special attention. Reviews (1): Last reviewed commit: "fix: keep session store live during rota..." | Re-trigger Greptile |
122f130 to
15de2b7
Compare
Summary
rename()withcopyFile()so the livesessions.jsonremains authoritative until the later atomic rewrite succeeds..bak.*rotation backups for diagnostics/rollback without introducing{}placeholders or backup-resurrection heuristics.Why
A rename-based rotation removes
sessions.jsonbeforewriteTextAtomic()writes the new store. If the gateway crashes in that window, startup can treat the session store as empty and lose the channel/thread -> transcript-file mapping.Copying the live file to
.bak.*before the atomic rewrite avoids that state entirely:sessions.jsonremains authoritativesessions.jsonis authoritativeFixes #68229.
Supersedes #71328.
Tests
pnpm test src/config/sessions/store.pruning.test.ts src/config/sessions/store.pruning.integration.test.ts src/config/sessions/sessions.test.tspnpm check:changed