Files
OmniRoute/tests/unit/db-backup-tempdir-mkdtemp.test.ts
Diego Rodrigues de Sa e Souza b3bee580a8 fix(api): create DB export temp paths with mkdtemp instead of predictable timestamps (#12579) (#13237)
Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on #13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for #13278 and #13276, annotated and landed on #13243

⚠️ base-red inherited: #12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
2026-09-11 22:05:22 -03:00

62 lines
2.5 KiB
TypeScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { test } from "node:test";
// Regression guard for #12579: DB export temp paths must be created via
// fs.mkdtempSync (unique + exclusive) rather than a predictable, deterministic
// timestamp-derived path passed to mkdirSync({ recursive: true }) or a raw
// write target. A deterministic path lets a local attacker pre-place a
// symlink at the predicted location; mkdirSync/writeFileSync then silently
// follow it (TOCTOU / symlink-following) instead of failing.
const exportAllSource = fs.readFileSync(
path.join(process.cwd(), "src/app/api/db-backups/exportAll/route.ts"),
"utf8"
);
const exportSource = fs.readFileSync(
path.join(process.cwd(), "src/app/api/db-backups/export/route.ts"),
"utf8"
);
test("exportAll/route.ts: uses fs.mkdtempSync to create the temp export directory", () => {
assert.match(exportAllSource, /fs\.mkdtempSync\(/);
});
test("exportAll/route.ts: never passes a manually-built timestamp path to mkdirSync", () => {
assert.doesNotMatch(exportAllSource, /fs\.mkdirSync\(\s*tempDir/);
});
test("export/route.ts: uses fs.mkdtempSync to create the temp export directory", () => {
assert.match(exportSource, /fs\.mkdtempSync\(/);
});
test("export/route.ts: the sqlite backup write target lives inside an mkdtemp-created directory, not a bare tmpdir path", () => {
assert.doesNotMatch(exportSource, /path\.join\(tmpDir,\s*exportFilename\)/);
});
test("mkdtempSync-based paths are unique across two calls made within the same millisecond (no timestamp collision)", () => {
const a = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-export-"));
const b = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-export-"));
try {
assert.notEqual(a, b);
} finally {
fs.rmSync(a, { recursive: true, force: true });
fs.rmSync(b, { recursive: true, force: true });
}
});
test("mkdtempSync rejects a pre-placed symlink at the target prefix path (exclusive creation, no TOCTOU)", () => {
// mkdtempSync always appends 6 random characters, so an attacker cannot
// predict (and therefore cannot pre-place a symlink at) the final path —
// unlike the old `mkdirSync(deterministicPath, { recursive: true })`.
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-export-"));
try {
assert.ok(fs.lstatSync(dir).isDirectory());
assert.ok(!fs.lstatSync(dir).isSymbolicLink());
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});