File upload security
Validate user uploads: MIME magic bytes, filename sanitization, size limits, separate serving domain, AV scanning, polyglot detectionFrom its SKILL.md
npx -y skills add ShieldNet-360/secure-vibe --skill file-upload-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.7 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
File Upload Security
Rules (for AI agents)
ALWAYS
- Verify magic bytes of every upload server-side.
Content-Typeand file extension are attacker-controlled and never sufficient. Use libmagic,file-type(Node),mimetypes-magic(Python), or Tika. - Maintain an allowlist of accepted types per endpoint
(
image/png,image/jpeg,application/pdf, …). Deny everything else, includingtext/html,image/svg+xml(carries<script>),text/xml, andapplication/octet-stream. - Sanitize filenames: strip directory components, normalize Unicode,
reject
.., NUL byte, control chars, reserved Windows names (CON,PRN,AUX,NUL,COM1-9,LPT1-9), and any non[a-zA-Z0-9._-]characters. Store as a UUID / hash and keep the original filename in a separate, escaped metadata column. - Enforce a size limit at the proxy / API gateway and at the application — at least double-layer. The proxy limit prevents bandwidth DoS; the app limit prevents memory exhaustion when a proxy is misconfigured.
- Store uploads outside the document root and serve them from a
separate domain (
usercontent.example.net) on a CDN. SetContent-Disposition: attachmentfor non-image types and aContent-Security-Policy: default-src 'none'; sandboxheader to neutralize any inline-rendered HTML/SVG. - Run a virus scanner (ClamAV, VirusTotal, Sophos) on every upload before making it accessible to other users — out of band so the request itself isn't latency-bound.
- Re-encode media server-side:
convert in.jpg out.jpg(ImageMagick with a strictpolicy.xml),ffmpeg -ifor video,pdftocairofor PDFs. Re-encoding strips most polyglot / steganographic payloads and exotic codec exploits. - For SVG specifically: either render server-side to a raster format,
or pass through a strict allowlist sanitizer (DOMPurify in Node,
lxml.html.cleanin Python) that strips<script>,<iframe>,<foreignObject>,xlink:hrefwithjavascript:, and CSS expression / url() with non-data URIs.
NEVER
- Trust
Content-Typefrom the client. The mime sniffer in IE / older Chrome reads the body for type clues — an HTML payload disguised asimage/pngwill run as HTML when served same-origin. - Construct the storage path with the user-supplied filename. Path
traversal (
../../etc/passwd) and the Windows reserved-name class both reduce to "let attacker pick where to write." - Serve uploads from the same origin as the application. Serving on
api.example.com/uploads/x.htmlmeans a malicious HTML upload runs with full access to api.example.com cookies and CORS. - Use a stack that processes uploads with ImageMagick / libraw / ExifTool / ffmpeg without strict policy.xml / sandbox / version control. ImageTragick (CVE-2016-3714) and GitLab ExifTool (CVE-2021-22205) both relied on a server happily handing user-controlled bytes to a media library.
- Allow PDF upload + render in-browser without verifying the PDF
passes structural validation (e.g.
pdfinfo). Malicious PDFs are a common JavaScript-in-PDF / XFA RCE primitive against Adobe Reader on the recipient side, even when the server is safe. - Use
.docx/.xlsx/.zipextraction withunziporpython -m zipfilewithout a path-traversal-safe extractor. Zip slip (CVE-2018-1002201) extracted files outside the target directory through../entries. - Use S3 / GCS presigned upload URLs without a strict
Content-Typesigned condition and a fixed object-key prefix. Without the conditions, the client can upload anything to any key.
KNOWN FALSE POSITIVES
- Internal-only admin uploads (e.g. an ops dashboard) may legitimately trust file extension because the trust boundary is the SSO + IP allowlist. Document this as a deliberate decision in the endpoint.
- Some integrations (e.g. exporting CSV from BI tools) need to round-trip user-supplied filenames; preserve them in metadata, but the on-disk name must still be a UUID.
- Tarballs / DEBs / RPMs in a build pipeline don't need AV scan — the trust boundary is the build pipeline's signing key, not the AV.
Context (for humans)
File upload is the persistent rich-target attack surface. Every
real-world breach lab includes a "find an upload form" early-game
move because the path from upload to RCE is usually short: upload an
HTML file with a JavaScript credential stealer, upload a PHP / JSP
shell to a misconfigured doc-root, upload an SVG with a
SAML-stealer <script>, upload an EXIF-payloaded image to a
vulnerable ImageMagick service.
The defenses are well-understood and inexpensive — the bug is that they have to be applied in combination. A magic-byte allowlist is trivially bypassed by a polyglot (a file that is simultaneously a valid PNG and a valid HTML page). A separate serving domain neutralizes the polyglot's HTML execution. A virus scanner catches known malware. Re-encoding strips weird codec payloads. Each defense is a layer; missing one layer turns most uploads from "stored data" into "stored RCE."
Verify & lock (triaging a finding)
A scanner/review hit is a candidate, not a confirmed bug. Confirm it, fix it, then lock it so it can't come back.
- Confirm it's real (probe the suspect endpoint). POST a payload the allowlist should reject and watch where it lands: a
shell.php/shell.jspwith a real magic-byte mismatch, animage/pngcontent-type wrapping HTML/SVG-with-<script>, or a filename like../../shell.php. Real if the upload is accepted and the stored object is reachable from an executable/same-origin path, the traversal escapes the upload dir, or the SVG/HTML renders script when fetched. False positive if magic bytes are verified server-side, the name is replaced with a UUID, and the file serves from a separate sandboxed domain asattachment— the probe is rejected or served inert. - Fix, then lock with a regression test (unit or integration — dev's call): feed the validator the disallowed cases and assert each is rejected —
.php/.jsp/.svg/.htmlextensions, spoofedContent-Typewhose magic bytes don't match, oversize body past the limit, and a../..// NUL / reserved-name filename (assert the on-disk key is a sanitized UUID, never the input). Then assert a benignimage/pngwith valid magic bytes still uploads and serves. Commit it to CI so the guard can't be silently dropped in a later refactor.
References
rules/upload_validation.json- OWASP File Upload Cheat Sheet.
- CWE-434.
- CWE-22 (Path Traversal).
- Snyk Zip Slip directory.
- ImageTragick (CVE-2016-3714).
What ships with it: 2 files
9.3 KB alongside SKILL.md
rules/
- upload_validation.json4.3 KB
tests/
- corpus.json5.0 KB