Files
SnapOtter/apps/api/src/lib/filename.ts
T
Siddharth Kumar Sah 6cfa3b0c38 feat: multi-arch Docker support, security hardening, and test improvements
Remove hardcoded --platform=linux/amd64 from Dockerfile so buildx produces
native arm64 images for Apple Silicon and Raspberry Pi. Add audit logging
for auth events, harden file storage with extension whitelists and
double-extension attack prevention, reject null-byte buffers in validation,
add data-testid attributes to all tool settings components, update
deployment docs with architecture notes and correct CI workflow references,
and fix unit test mock to match throwWithMessage error extraction.
2026-03-28 11:19:09 +08:00

50 lines
1.3 KiB
TypeScript

import { basename } from "node:path";
const SAFE_IMAGE_EXTENSIONS = new Set([
".jpg",
".jpeg",
".png",
".webp",
".gif",
".bmp",
".tiff",
".tif",
".avif",
".svg",
".pdf",
]);
/**
* Sanitize a filename to prevent path traversal and double-extension attacks.
*
* 1. Strips directory separators (basename only).
* 2. Removes ".." sequences and null bytes.
* 3. Truncates after the first recognised image extension so that
* "photo.png.php" becomes "photo.png".
*/
export function sanitizeFilename(raw: string): string {
let name = basename(raw);
name = name.replace(/\.\./g, "");
name = name.replace(/\0/g, "");
if (!name || name === "." || name === "..") {
name = "upload";
}
// Guard against double-extension attacks (e.g. "image.png.php").
// Walk the dot-separated parts and truncate after the first safe image extension.
const dotIndex = name.indexOf(".");
if (dotIndex !== -1) {
const parts = name.split(".");
for (let i = 1; i < parts.length; i++) {
const ext = `.${parts[i].toLowerCase()}`;
if (SAFE_IMAGE_EXTENSIONS.has(ext)) {
// Keep everything up to and including this extension, drop the rest
name = parts.slice(0, i + 1).join(".");
break;
}
}
}
return name;
}