UNPKG

@stacksjs/router

Version:
65 lines (64 loc) 2.29 kB
/** * Validate and return a path parameter, throwing if it's unsafe to use * in filesystem interpolation. * * The default contract is single-segment: no `/`, no `\`, no `..`, no * absolute path, no null bytes, no control characters, length ≤ 255. * Pass `allowSlashes: true` for a multi-segment path (still rejects * the rest). * * @throws {PathParamError} when the value fails any check. */ export declare function sanitizePathParam(value: unknown, options?: SanitizePathParamOptions): string; /** * Non-throwing variant. Returns the sanitized value or `null` if any * check failed. Use when you want a fast yes/no in a conditional * without a try/catch around the throw site. */ export declare function safePathParam(value: unknown, options?: SanitizePathParamOptions): string | null; export declare interface SanitizePathParamOptions { context?: string maxLength?: number allowSlashes?: boolean } /** * Path-parameter sanitization helpers. * * Route params arrive from the URL as untyped strings and are merged * directly into `req.params`. Actions that interpolate those values * into filesystem paths or shell commands without first scrubbing * them are vulnerable to `..`-traversal, absolute-path takeovers, and * null-byte truncation attacks. * * The router itself can't auto-sanitize every param (some are * deliberately path-shaped — file servers, asset proxies, etc.). What * we ship instead is a single canonical helper that callers reach for * at the boundary where the param meets the filesystem. * * See stacksjs/stacks#1870 R-12. * * @example * ```ts * import { sanitizePathParam } from '@stacksjs/router' * * const filename = sanitizePathParam(req.params.filename, { * context: 'avatar download', * }) * return new Response(Bun.file(path.appPath(`avatars/${filename}`))) * ``` */ /** * Reasons {@link sanitizePathParam} rejects a value. Surfaced via the * thrown error so callers can log or branch. */ export type PathParamRejection = | 'empty' | 'not-string' | 'absolute-path' | 'traversal' | 'null-byte' | 'control-char' | 'too-long'; export declare class PathParamError extends Error { readonly reason: PathParamRejection; constructor(reason: PathParamRejection, value: unknown, context?: string); }