An upload handler that trusts the file's name and type, and stores it where the server can execute or serve it, is a common path to remote code execution or stored cross-site scripting. Validate the type against an allowlist, give the file a new random name, and store it somewhere it will never be executed.
Why it's a problem
If an attacker can upload a script and then request it, the server may execute it, which is full compromise. Even without execution, an uploaded HTML or SVG file served from your domain can carry cross-site scripting. Trusting the original filename also invites path traversal, writing the file outside the intended folder.
The pattern
// trusts the uploaded name and type
const dest = path.join("public/uploads", file.originalname);
fs.writeFileSync(dest, file.buffer);The fix
// allowlist type, random name, non-served storage
const allowed = { "image/png": ".png", "image/jpeg": ".jpg" };
const ext = allowed[file.mimetype];
if (!ext) return res.sendStatus(400);
const name = crypto.randomUUID() + ext;
fs.writeFileSync(path.join("/var/uploads", name), file.buffer);Why AI tools write this
Saving the upload under its original name into a public folder is the most direct way to make an upload feature work and be visible, so it is the natural completion. It works fine for ordinary images in testing, and the danger only appears with a malicious file.
The quick fix
- Validate the content type against an allowlist; do not trust the file extension alone.
- Give each upload a new random filename, never the user-supplied one.
- Store uploads outside the web root so they cannot be executed or directly served.
Common questions
Why do I need to check MIME type if I am already checking the file extension?
A user can rename a .php or .js file to .jpg. Verify the actual content type by reading the file's magic bytes, not just the extension or the Content-Type the client sent.
Is storing uploads in an S3 bucket automatically safe?
Safer than storing in the web root, but only if the bucket is not publicly readable and content is not served with executable MIME types. Confirm the bucket policy does not allow anonymous reads.
How do I generate a safe random filename for uploads?
Use crypto.randomUUID() or crypto.randomBytes(16).toString('hex') appended with the validated extension. Never reuse or trust the user-supplied filename.