refactor(security): harden temp-path handling for inbound media

This commit is contained in:
Peter Steinberger
2026-02-19 14:06:11 +01:00
parent 9f9cd5cbb2
commit ec232a9e2d
10 changed files with 235 additions and 41 deletions

View File

@@ -1,4 +1,5 @@
import crypto from "node:crypto";
import { mkdtemp, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -20,6 +21,12 @@ function sanitizeExtension(extension?: string): string {
return `.${token}`;
}
function sanitizeFileName(fileName: string): string {
const base = path.basename(fileName).replace(/[^a-zA-Z0-9._-]+/g, "-");
const normalized = base.replace(/^-+|-+$/g, "");
return normalized || "download.bin";
}
export function buildRandomTempFilePath(params: {
prefix: string;
extension?: string;
@@ -37,3 +44,22 @@ export function buildRandomTempFilePath(params: {
const uuid = params.uuid?.trim() || crypto.randomUUID();
return path.join(params.tmpDir ?? os.tmpdir(), `${prefix}-${now}-${uuid}${extension}`);
}
export async function withTempDownloadPath<T>(
params: {
prefix: string;
fileName?: string;
tmpDir?: string;
},
fn: (tmpPath: string) => Promise<T>,
): Promise<T> {
const tempRoot = params.tmpDir ?? os.tmpdir();
const prefix = `${sanitizePrefix(params.prefix)}-`;
const dir = await mkdtemp(path.join(tempRoot, prefix));
const tmpPath = path.join(dir, sanitizeFileName(params.fileName ?? "download.bin"));
try {
return await fn(tmpPath);
} finally {
await rm(dir, { recursive: true, force: true }).catch(() => {});
}
}