A reviewer should check eight things in upload code: an extension allow list, no trust in the client supplied content type, a server generated filename, storage outside the web root, size limits, content validation, an authenticated and authorized uploader, and scanning where it fits. Missing any one is a reason to comment.
The checklist, from OWASP
The OWASP File Upload Cheat Sheet groups its advice in a way that maps cleanly to review comments.
| Control | What to look for in the diff |
|---|---|
| Extension allow list | A short list of permitted types, validated after decoding the filename. Block lists alone are weak |
| Content type | The Content-Type header is not trusted. It comes from the client and is easy to spoof |
| Filename | The server generates the stored name, such as a random UUID, so user input never controls the path |
| Storage location | Outside the web root, ideally a separate host, with per directory config overrides disabled |
| Size limits | A cap on upload size, and on decompressed size for archives |
| Content validation | Images re-encoded to an allowed format with metadata stripped |
| Authentication | A known user who is authorized to upload and to access the files |
| Scanning | Antivirus or sandbox checks, and content disarm for document types where applicable |
Reading the code
- Start with the route. Is it behind authentication? Many upload bugs are simply public endpoints.
- Find where the filename is used. If any part of the stored path comes from the request, flag it.
- Find the type check. If it reads the header the client sent, ask for a check on the actual file.
- Find the storage call. Check the target directory and whether files can be served and executed from it.
- Find the limits. Look for a maximum size at the web server and in application code.
- Check what happens after upload. Files that are previewed, converted or unpacked get parsed by libraries, which is its own attack surface.
# risky: the client controls the name and location
path = os.path.join(UPLOAD_DIR, request.files["f"].filename)
# safer: the server controls both
path = os.path.join(UPLOAD_DIR, str(uuid.uuid4()) + ".png")
Archives and documents
The cheat sheet advises avoiding ZIP files where possible and checking decompressed sizes, because a small archive can expand into a very large one. For document types such as PDF and DOCX, it mentions content disarm and reconstruct as an option. These are heavier controls, so decide by risk. A profile picture endpoint and a document sharing feature deserve different levels of care.
Tests worth asking for
Ask for a test that uploads a file with a mismatched extension and content, one with a traversal sequence in the name, and one over the size limit. If the tests do not exist, the controls often do not either.
Try it yourself
Run the web scan in the demo and look at how a dynamic scan reports issues on upload forms and responses.
Click through it at your own pace, or open it full screen.
Where Safeguard fits
Safeguard scans your dependencies, including image and document libraries your upload code relies on. Start with the free scan at gold.safeguard.sh/scan.
Quick check
Can I trust the Content-Type header on an upload?
No. OWASP notes it comes from the client and is easy to spoof, so verify the actual file.
Why rename uploaded files?
So user input never controls the stored path or name.
Sources
Last reviewed: 19 October 2026.