mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 13:23:50 +03:00
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.
62 lines
2.5 KiB
TypeScript
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 });
|
|
}
|
|
});
|