Review: File Download Route Written by an AI Assistant
Your team asked an AI coding assistant to let users download the files they have uploaded to a document-sharing app. It opened this pull request. You are the reviewer, and nothing merges without your approval.
The assistant's description:
Adds GET /download?file=.... Files are read from the signed-in user's own folder, so users can only ever reach their own uploads. Supports sub-folders, sets the right content type so PDFs and images open in the browser, and streams the file instead of buffering it.
Where it runs: Express 4 on Node 20, one process per container, with sessions held in a cookie on the same domain as the rest of the app. Users can upload any file type. The router is mounted without any async error wrapper.
Your task
- Flag every problem you would block the merge for. Click a line number to comment, and Shift-click to cover a range.
- For each one, choose the issue type and severity, and explain what fails and how to fix it.
- You are scored on the problems you catch, the right issue type and a clear explanation. Missing a critical problem fails the round.
Aim for 10 minutes, as in a real review round.
The description says users can only reach their own folder. Try file=../7/payslip.pdf.
What does Express 4 do when an async handler's promise rejects? Ask for a file that does not exist.
A user uploads notes.html and shares the download link. What does the browser do with it?
pipe does not pass errors along. What happens if the file vanishes between stat and the read?
A strong reviewer blocks this on two grounds straight away: any user can read any file the process can read, and any user can take the server down with one request.
- Path traversal (line 23, critical).
path.joinresolves.., sofile=../7/payslip.pdfreads another user's file andfile=../../../etc/passwdleaves the uploads folder completely. Resolve the path and check that it still starts withuserDir + path.sep, or look files up by id in the database instead of by name. - Unhandled rejection crashes the process (line 25, critical).
fs.promises.statrejects withENOENTfor a missing file. Express 4 does not catch rejected promises from handlers, so the request hangs and Node 20 exits on the unhandled rejection. One mistyped name takes the container down. Wrap the handler in try/catch and return 404 forENOENT, or forward errors withnext(err). - User uploads served inline with their own type (lines 31–33, major). An uploaded
.htmlor.svgfile is served astext/htmlorimage/svg+xml, inline, on the app's own domain. Opening a shared link runs the uploader's script with the victim's session: stored XSS. SendContent-Disposition: attachment(for exampleres.attachment(name)) andX-Content-Type-Options: nosniff, allow inline only for a short list of safe types, or serve uploads from a separate domain. - Read stream errors are not handled (line 35, minor).
pipedoes not forward errors. If the file is deleted or unreadable after thestat, the stream emitserrorwith no listener and the process crashes. Usestream.pipelinewith a callback, orres.sendFilewith a root option.