/** * Sanitize a `dir` parameter for `presignedUploadUrl`. Returns the * cleaned dir on success (trailing slashes stripped, empty string * preserved as-is) or throws `PathSanitizeError` on: * * - non-string input * - absolute paths (`/foo`) * - traversal segments (`..`, `foo/../bar`) * - null bytes (`foo\0bar`) — these get stripped by some S3 SDKs * silently * - control characters (`\r`, `\n`, etc.) — log-injection risk * - segments outside `[A-Za-z0-9._-]` * * Empty / undefined dir is allowed (it means "write at the root of * the configured prefix"). */ export declare function sanitizePresignedDir(dir: string | undefined): string; /** * Sanitize a `filename` parameter for `presignedUploadUrl`. Returns * the cleaned filename on success or throws `PathSanitizeError`. * * Rejects path separators, traversal tokens, null bytes, control * characters, and disallowed characters. Validates the extension * against a strict alphanumeric pattern (no `.exe.jpg` smuggling — * the caller is responsible for matching extension to expected * content type via the contentType the URL was signed for). */ export declare function sanitizePresignedFilename(filename: string): string; /** * Parse a `disk:path` reference used by `Storage.copyAcross()` / * `moveAcross()` (stacksjs/stacks#1888 S-7). * * Format: `:` where: * - `` is an alphanumeric + dash/underscore disk name * - `` is a storage-relative path (path-traversal / * null-byte / control-char checks applied) * * Throws {@link PathSanitizeError} on a malformed input — the * cross-disk helpers turn that into a clear "bad source" / "bad * dest" error rather than crashing inside the adapter. * * @example * ```ts * parseDiskPath('s3:user-uploads/foo.jpg') * // → { disk: 's3', path: 'user-uploads/foo.jpg' } * ``` */ export declare function parseDiskPath(input: string): ParsedDiskPath; /** * Parsed `disk:path` reference returned by {@link parseDiskPath}. */ export declare interface ParsedDiskPath { disk: string path: string } /** * Path-sanitization helpers for storage adapters (stacksjs/stacks#1873). * * Callers of `presignedUploadUrl({ dir, filename })` pass in * caller-controlled strings that get interpolated straight into the * stored key. Without sanitization, `dir: '../../sensitive'` escapes * the intended prefix and `filename: 'foo/bar.exe'` injects a * directory separator — both let a hostile caller (or a confused * authenticated caller) write to objects outside their intended * scope. These helpers reject the dangerous shapes loudly before the * adapter ever signs anything. * * Note: the local/bun adapters already have a defense-in-depth check * via `path.relative()` in `resolvePath()`. The S3 adapter doesn't, * because S3 keys are opaque strings — there's no filesystem `..` * resolution to lean on. That's exactly why we need this layer. */ /** * Thrown when `sanitizePresignedDir` or `sanitizePresignedFilename` * detects a value that would escape the intended scope. The `reason` * discriminant lets callers distinguish "you passed an absolute path" * from "you passed a null byte" if they want to surface that in error * messages — most callers can just `catch (e: PathSanitizeError)` and * return a 400. */ export declare class PathSanitizeError extends Error { readonly reason: | 'empty' | 'not-string' | 'absolute-path' | 'traversal' | 'null-byte' | 'control-char' | 'too-long' | 'invalid-char' | 'invalid-extension'; constructor(message: string, reason: PathSanitizeError['reason']); }