Accept, store, process, and serve user uploads safely: type-appropriate content validation, decompression limits, generated storage keys, quarantine before publication, isolated media processing, and scoped direct-to-storage credentials. Use when generating a file-upload endpoint, wiring presigned or SAS uploads to S3, GCS, or Azure Blob, processing user-supplied images or documents, or serving user-generated content.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add ShieldNet-360/secure-vibe --skill file-upload-security --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of File Upload Security?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-file-upload-security-secure-vibe)More formats (shields.io, HTML) on the badges page.
---
name: file-upload-security
description: "Accept, store, process, and serve user uploads safely: type-appropriate content validation, decompression limits, generated storage keys, quarantine before publication, isolated media processing, and scoped direct-to-storage credentials. Use when generating a file-upload endpoint, wiring presigned or SAS uploads to S3, GCS, or Azure Blob, processing user-supplied images or documents, or serving user-generated content."
---
<!-- Native skill bundle for Claude Code. Generated by `secure-vibe dev regenerate`. -->
<!-- Do not edit by hand; the source of truth is skills/file-upload-security/SKILL.md. -->
# File Upload Security
Accept, store, process, and serve user uploads safely: type-appropriate content validation, decompression limits, generated storage keys, quarantine before publication, isolated media processing, and scoped direct-to-storage credentials. Use when generating a file-upload endpoint, wiring presigned or SAS uploads to S3, GCS, or Azure Blob, processing user-supplied images or documents, or serving user-generated content.
## ALWAYS
- Put every new upload in a **non-public quarantine** location, and do not expose it, link it, index it, or hand it to a downstream workflow until validation has succeeded. Quarantine → validate → process → publish, in that order. This is the invariant the rest of these rules hang from, and the one direct-to-storage uploads most often skip.
- Validate content **server-side with checks appropriate to the format**. Where a format has a reliable signature, verify it; for container and document formats — DOCX, XLSX and ZIP are all ZIP at the container level — also validate structure against the format you expect. Text-based formats such as SVG and CSV have no signature to check at all, so a signature check alone is not a content policy. The client's `Content-Type` and the filename are attacker-controlled and are never the check.
- Keep a **per-endpoint allowlist** of accepted types and deny everything else. Deny `image/svg+xml` by default: an SVG is a document that can carry script. Where an endpoint genuinely requires SVG, treat it as active content — rasterize it, or run a maintained **SVG-aware** sanitizer with an explicit element, attribute, URL and CSS allowlist. A generic HTML sanitizer is the wrong tool for it.
- Bound the **expansion**, not only the upload. For archives and compressed or container formats, cap expanded bytes, entry count, nesting depth, compression ratio, per-entry size and processing time, and reject before extracting when the projected expansion exceeds policy. A limit on the compressed bytes says nothing about what they become (CWE-409).
- Store under an **application-generated identifier** — a UUID or content hash — outside the executable or web document root, and never build the storage path from the user-supplied name. Keep the original filename as bounded metadata only, and encode it for the specific output context where it is displayed. There is no "escaped at rest" that is safe in every context.
- Run every parse, re-encode or extraction of untrusted bytes in an **isolated worker**: least privilege, no unnecessary network, and CPU, memory and wall-clock limits. The decoder is the attack surface, so invoking one is not itself the defense — ImageTragick (CVE-2016-3714) and the GitLab ExifTool RCE (CVE-2021-22205) were both a server handing user bytes to a media library. `container-security` owns what that isolation looks like.
- Scan for malware where the threat model calls for it, out of band, with the object still quarantined. Do **not** send uploads to a third-party or public scanning service — VirusTotal among them — unless the data classification explicitly permits disclosing that content to that provider. Submitting a customer contract for scanning is disclosing the contract.
- Serve according to what the content is. Untrusted content a browser may render inline belongs on an **isolated, cookie-less origin**, with a server-determined `Content-Type`, `X-Content-Type-Options: nosniff`, `Content-Disposition: attachment` wherever inline rendering is not required, and `Content-Security-Policy: default-src 'none'; sandbox`. A private document behind an authorizing application handler is a different and equally acceptable architecture. `nosniff` narrows type sniffing; it does not make a shared origin safe for active content.
- For **direct-to-storage** uploads, scope the credential with the provider's own mechanism to the intended object, operation and a short lifetime, and apply provider-side size constraints where they exist. A signed `Content-Type` binds the header the client sends, not the bytes — the stored object still has to be validated server-side before it leaves quarantine, and your gateway and application size limits do not sit on this path at all.
- Where the "upload" accepts a **URL** and the server fetches it, apply `ssrf-prevention` before the fetch, with size and time limits on the response. Validation starts on what comes back. `api-security` owns who may upload and who may read the result.
## NEVER
- Treat the client's `Content-Type` or the filename extension as the type check.
- Treat successful parsing as proof of safety. A structurally valid PDF is still a PDF that can carry active content; `pdfinfo` answers "is this parseable", not "is this safe to render or redistribute".
- Extract an archive without a path-traversal-safe extractor. Zip Slip (CVE-2018-1002201) wrote outside the target directory through `../` entries — a separate problem from how much the archive expands, and both need handling.
- Publish, link, index, or pass downstream an object that has not left quarantine.
## KNOWN FALSE POSITIVES
- An integration that must round-trip the user's filename: preserve it as metadata and encode it at the point of display. The stored object key is still generated.
- Signed build artifacts (tarballs, DEBs, RPMs) may rely on supply-chain signature verification instead of commodity AV where policy defines that trust model. Verify the signature **before** parsing or extracting, and note that it waives nothing else — safe extraction, decompression limits and sandboxed processing all still apply. A signature proves origin, not that the bytes are harmless.
## Reference files
Read these only when the task calls for them.
- `references/direct-upload.md`
- `references/format-handling.md`
- `references/verifying-findings.md`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!