/** * Input validation for every user-supplied identifier that ends up on the * filesystem path. These two are the ONLY allowed shapes for module names * and versions on this registry; both must be enforced at every public * entry point in server.ts (publish, download, metadata, yank, unyank, * sparse index) because `storage.packagePath()` / `storage.indexPath()` * feed their inputs straight to `node:path.join`, which happily normalizes * `..` segments and escapes the data directory. * * Keep these as pure regex predicates so there is no way to accidentally * skip them — if the string passes, the path is guaranteed to contain * only safe chars (lowercase alphanum + `-` / digits + `.` / `+`). No * slashes, no `..`, no control chars. */ /** Module names: kebab-case lowercase, no leading/trailing hyphen. */ const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/; /** * Module versions: optional Debian-style epoch (`N:`), then semver * (major.minor.patch), then a literal `+`, then a numeric pkg revision. * Examples: `1.4.2+1`, `1.0.0+12`, `2:1.0.0+1` (epoch bump). * Rejects: `1.0.0+abc`, `1.0.0+1/../etc`, `1.0.0 +1`, empty, traversal. */ const VERSION_REGEX = /^(\d+:)?\d+\.\d+\.\d+\+\d+$/; /** Max bytes we'll accept for either identifier — far beyond any real use. */ const MAX_IDENT_LENGTH = 128; export function isValidName(name: string): boolean { return name.length > 0 && name.length <= MAX_IDENT_LENGTH && NAME_REGEX.test(name); } export function isValidVersion(vers: string): boolean { return vers.length > 0 && vers.length <= MAX_IDENT_LENGTH && VERSION_REGEX.test(vers); } /** Throw-style version for handler call sites — returns the offending field name. */ export function validateNameAndVersion( name: string, vers: string, ): { ok: true } | { ok: false; field: 'name' | 'vers'; message: string } { if (!isValidName(name)) { return { ok: false, field: 'name', message: 'Invalid module name — must be kebab-case lowercase, 1-128 chars (e.g. "my-module")', }; } if (!isValidVersion(vers)) { return { ok: false, field: 'vers', message: 'Invalid version — must match "..+" (e.g. "1.4.2+1")', }; } return { ok: true }; }