T09 · Insecure Skill Coding Practices
- Location
references/uploads.md:78- Finding
Attacker-Controlled Filename Used for Disk-Backed Upload Storage
- Content
View full analysis
Vulnerability Details
File Location:
references/uploads.md, lines 78-83
Vulnerability Type: Unsafe temporary-file path and filename handling
Risk Level: MediumVulnerable Code
ts const uploadHandler = unstable_createFileUploadHandler({ directory: "/tmp/uploads", maxPartSize: 10_000_000, // 10 MB file: ({ filename }) => filename, filter: ({ contentType }) => contentType.startsWith("image/"), // callbacks also receive `name` });Technical Analysis
The disk-backed upload example passes the client-supplied multipart filename directly to the upload handler:
ts file: ({ filename }) => filenameMultipart filenames are attacker-controlled and must not be used as trusted filesystem identifiers. Depending on the upload library's path normalization and collision behavior, crafted filenames could cause path manipulation, overwrite existing files, create ambiguous file ownership, or interfere with concurrent uploads.
The
contentType.startsWith("image/")filter does not provide a security boundary because the multipart content type is also supplied by the client. Although the explicit 10 MB limit reduces resource-exhaustion risk, it does not mitigate unsafe filename selection or malicious file content.Attack Path
- An attacker submits a multipart request to an action implemented from this example.
- The attacker supplies an
image/*content type to pass the filter. - The attacker chooses a filename designed to collide with another upload or, where runtime path handling permits, containing path-separator or traversal components.
- The server passes that filename directly to the disk upload handler.
- The handler writes the uploaded bytes using the attacker-influenced destination name.
- The attacker may overwrite or interfere with files writable by the server process within the effective upload path. Broader path access depends on the library's path co ...[truncated 709 chars]
- Remediation
View remediation
Remediation Suggestions
- Never use the multipart filename as the stored filename. Generate a cryptographically random server-side identifier, such as a UUID.
- Store the original filename only as sanitized metadata when it is needed for display.
- Resolve the generated destination against a fixed upload directory and verify that the normalized result remains inside that directory.
- Reject filenames containing path separators, traversal sequences, control characters, null bytes, reserved names, or unsupported extensions.
- Use exclusive file creation or another collision-resistant mechanism rather than allowing replacement of an existing path.
- Do not trust the declared multipart content type. Validate file signatures or magic bytes and process images through a trusted decoder.
- Apply authorization checks before accepting an upload and associate each temporary file with the authenticated user or request.
- Move accepted files into controlled permanent storage and reliably delete temporary files on success, validation failure, and exceptions.
- Retain explicit upload-size limits and consider aggregate request, rate, and storage quotas.
A safer filename callback would resemble:
ts import { randomUUID } from "node:crypto"; const uploadHandler = unstable_createFileUploadHandler({ directory: "/tmp/uploads", maxPartSize: 10_000_000, file: () => randomUUID(), filter: ({ contentType, name }) => name === "attachment" && contentType.startsWith("image/"), });File content must still be validated after upload because both the filename and declared content type are attacker-controlled.
