Sanitize Upload Filenames

Make untrusted upload names safe by stripping paths, control characters, reserved names and dangerous extensions.

TypescriptFile Upload

Code

snippet
const RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;

export interface SanitizeOptions {
  maxLength?: number;
  replacement?: string;
  fallback?: string;
}

export function sanitizeFilename(input: string, options: SanitizeOptions = {}): string {
  const { maxLength = 120, replacement = "_", fallback = "file" } = options;
  if (!Number.isInteger(maxLength) || maxLength < 1) {
    throw new RangeError("maxLength must be a positive integer");
  }
  if (!replacement || /[\\/:*?"<>|]/.test(replacement)) {
    throw new TypeError("replacement contains forbidden characters");
  }
  const normalized = String(input).normalize("NFC").replace(/[\u0000-\u001f\u007f]/g, "");
  const base = normalized.split(/[\\/]/).pop() ?? "";
  let name = base
    .replace(/[<>:"|?*]/g, replacement)
    .replace(/\s+/g, " ")
    .trim()
    .replace(/^\.+/, "")
    .replace(/\.+$/, "");
  if (!name) return fallback;
  const dot = name.lastIndexOf(".");
  const extension = dot > 0 ? name.slice(dot).slice(0, 16) : "";
  let stem = dot > 0 ? name.slice(0, dot) : name;
  if (RESERVED_NAMES.test(stem)) stem = "_" + stem;
  const budget = Math.max(1, maxLength - extension.length);
  stem = stem.slice(0, budget).trim();
  if (!stem) stem = fallback;
  name = stem + extension;
  return name;
}