Skip to content

Repository files navigation

sanitise-path

Zero-dependency, pure-JS sanitisation for file names and paths, optimised for performance. Works in Node, Bun, Deno and the browser. No node:path, no buffers, no runtime deps.

British spelling exports (sanitise*) with US aliases (sanitize*).

Install

bun add sanitise-path

API

import {
  sanitiseFilename, // sanitizeFilename
  sanitisePath,     // sanitizePath
  sanitise,
  truncateUtf8Bytes,
} from "sanitise-path";

sanitiseFilename(name, opts?) → string

Sanitises a single file name (not a path). Returns string.

sanitiseFilename('hello?.txt')       // 'hello.txt'  (illegal chars stripped)
sanitiseFilename('con.txt')          // ''           (reserved device name)
sanitiseFilename('myfile. ')         // 'myfile'     (trailing dot/space trimmed)

sanitisePath(path, opts?) → string

Sanitises a file path as path text. It does not resolve, normalise, or canonicalise paths. Pure-JS, deterministic POSIX-style output on every OS.

sanitisePath('../etc/passwd')             // 'etc/passwd'        (`..` segment dropped)
sanitisePath('a/../b')                    // 'a/b'               (no resolution)
sanitisePath('a/../b', { resolve: true }) // 'b'                 (opt-in resolution)
sanitisePath('%2e%2e%2fetc')              // '%2e%2e%2fetc'      (percent stays literal)
sanitisePath('/var/app/..')               // 'var/app'           (trailing '..' dropped)
sanitisePath("/path/$dir!/file|name.txt") // 'path/$dir!/filename.txt'

sanitise(filePath, fileName, opts?) → { path, name }

Convenience wrapper — one call for both. Passes the same opts to each.

sanitise("dir/../up", "report?.pdf")
// => { path: "dir/up", name: "report.pdf" }

truncateUtf8Bytes(str, maxBytes) → string

Truncates to at most maxBytes UTF-8 bytes without splitting a surrogate pair. Returns the input unchanged when it already fits.

truncateUtf8Bytes("a".repeat(300), 245) // 245 chars
truncateUtf8Bytes("😀".repeat(100), 245) // 61 emoji (244 bytes), never a partial pair

Options

Each option is optional. sanitiseFilename supports all options. sanitisePath supports replacement, reserveWindowsNames, and resolve, and emptyFallback; it does not apply maxBytes to paths or path segments.

Option Type Default Description
replacement string (none) Inserted per removed char instead of stripping. E.g. replacement: "_" turns a/b into a_b.
maxBytes number (none) Truncate the file name to at most this many UTF-8 bytes (safe around surrogate pairs). Applies to names only. No truncation by default.
reserveWindowsNames boolean true Strip reserved device names: con prn aux nul com0-9 lpt0-9 (± extension, case-insensitive). For paths, this applies per segment. Set false to keep them.
resolve boolean false Path only. Lexically resolve .. against previous emitted segments. Percent sequences still stay literal.
emptyFallback string (none) Returned when the final result would be ''. For paths, this applies only to the whole final path, not to individual dropped segments.
sanitiseFilename("a/b", { replacement: "_" })             // "a_b"
sanitiseFilename("a".repeat(300), { maxBytes: 245 })      // truncated to 245
sanitiseFilename("con", { reserveWindowsNames: false })   // "con"
sanitiseFilename("   ", { emptyFallback: "untitled" })    // "untitled"

replacement: "" — an empty-string replacement is equivalent to stripping (chars are simply removed). It is not inserted literally.

Behaviour contract

Name pipeline (in order): 1) strip/replace illegal + control chars (\ / ? < > : * | " + C0/C1 controls), 2) trim trailing dots/spaces, 3) truncate to maxBytes, 4) reserved-name check (if enabled) → '', 5) emptyFallback if result is ''.

Path rules:

  • Backslashes are treated as separators and emitted as /.
  • Empty segments from leading, trailing, or repeated slashes are dropped.
  • Each path segment is sanitised with the filename rules, excluding maxBytes.
  • Empty, ., .., illegal-only, and reserved-name segments are dropped.
  • Segments are never resolved: a/../ba/b, a/b/..a/b, ../../etcetc.
  • Set resolve: true to lexically resolve .. against previous emitted segments: a/../bb, a/b/..a, a/..'', ../../etcetc.
  • Percent sequences stay literal: %2e%2e/%2f is not decoded before sanitising.
  • emptyFallback applies to the final whole path only: sanitisePath('', { emptyFallback: 'x' })'x', while sanitisePath('a/con/b', { emptyFallback: 'x' })'a/b'.

Browser vs Node

Pure JS — no node:path, no Buffer. Bundlers (Vite/webpack/ESBuild/Bun) will tree-shake it cleanly.

Security notes

  • Output never contains a leading /, a ./.. component, or a Windows reserved device name segment (unless disabled).
  • Path output is POSIX-style and host-independent, so the same input sanitises identically on Windows/macOS/Linux.
  • This sanitises path text only. It is not a substitute for node:path, path resolution, filesystem containment checks, an allow-list, or permission checks.
  • resolve: true is lexical segment resolution only. It does not prove that a filesystem path remains contained under a directory after symlinks or platform filesystem semantics are applied.

Benchmark

Run with bun bench (powered by mitata). Lower is better — avg time per iteration over the shared realistic input suites in benchmarks/bench.ts.

Development + Contributing

This package is intentionally small: zero runtime dependencies, pure JS output, and deterministic POSIX-style path text on every platform. Contributions should preserve that shape unless there is a clear reason to change the public contract.

Local setup

Use Bun for package management, tests, and builds:

bun install
bun run typecheck
bun test
bun run build

bun run build regenerates dist/ by bundling src/index.ts to ESM and emitting TypeScript declarations.

Before opening a PR

  • Keep runtime code dependency-free. Do not import node:*, Buffer, or a runtime package from src/.
  • Keep behaviour deterministic across operating systems. Path output should stay POSIX-style (/) and must not depend on the host filesystem.
  • Update tests for every behaviour change. The behaviour contract above should match src/sanitise.ts and test/sanitise.spec.ts.
  • Update this README and CHANGELOG when public API, options, security notes, or observable behaviour changes.
  • Run bun run typecheck && bun test. Run bun run build when preparing a publishable package.

Implementation notes

src/sanitise.ts is optimised for usage in hot paths. Prefer small, direct changes over new abstractions. The implementation deliberately uses precomputed character lookup tables and direct UTF-16 code-unit scanning to avoid regex and Buffer overhead in runtime code.

src/index.ts is the package entrypoint. British exports (sanitise*) are the canonical API; US spelling is provided only as explicit alias bindings.

License

MIT

About

Zero-dependency, pure-JS file name and path sanitisation with configurable options.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages