fix: remediate Path Traversal in src/routes/reports.js - #103
devin-ai-integration[bot] wants to merge 2 commits into
Conversation
- Fix CWE-22 path traversal in /download route (CodeQL alert #34) by resolving the path and validating it stays within /reports root - Fix CWE-22 path traversal in /view route (CodeQL alert #35) by resolving the path and validating it stays within /reports root Both fixes use path.resolve() to normalize the path and then verify the resolved path starts with the allowed root directory, preventing directory traversal attacks using ../ sequences. Co-Authored-By: yubinkjee <yubinkjee@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
- Add express-rate-limit dependency and apply rate limiter to /view route (fixes CodeQL 'missing rate limiting' alert) - Replace path.resolve + startsWith check with path.basename sanitization to fully strip directory traversal from user input before constructing the file path (fixes CodeQL 'uncontrolled data in path expression' alert) - Add input validation for missing/non-string path parameter Co-Authored-By: yubinkjee <yubinkjee@gmail.com>
| const filename = req.query.file; | ||
| const filePath = path.join('/reports', filename); | ||
| const filePath = path.resolve(REPORTS_ROOT, filename); |
There was a problem hiding this comment.
🟡 Missing input validation in /download causes unhandled TypeError when req.query.file is undefined
When req.query.file is not provided (e.g., GET /reports/download with no query params), filename is undefined. Calling path.resolve(REPORTS_ROOT, undefined) throws TypeError: Path must be a string. Received undefined. Since there is no try-catch around this code and no global error handler in src/app.js:25-29, Express will catch it and return a generic 500 error.
Root Cause and Comparison with /view Route
The /view route (line 47) correctly guards against missing input:
if (!reportPath || typeof reportPath !== 'string') {
return res.status(400).json({ error: 'Missing file path' });
}But the /download route at line 30-31 has no such guard:
const filename = req.query.file; // undefined if not provided
const filePath = path.resolve(REPORTS_ROOT, filename); // throws TypeErrorImpact: Any request to /reports/download without a file query parameter will throw an unhandled exception, resulting in a 500 error instead of a clean 400 response. This is a regression in error handling introduced by this PR — the old path.join had the same issue, but since this PR is specifically fixing this route and added the guard to /view, the omission here is inconsistent and should be addressed.
| const filename = req.query.file; | |
| const filePath = path.join('/reports', filename); | |
| const filePath = path.resolve(REPORTS_ROOT, filename); | |
| const filename = req.query.file; | |
| if (!filename || typeof filename !== 'string') { | |
| return res.status(400).json({ error: 'Missing file parameter' }); | |
| } | |
| const filePath = path.resolve(REPORTS_ROOT, filename); |
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Closing due to inactivity for more than 7 days. Configure here. |
fix: remediate Path Traversal in src/routes/reports.js
Summary
Fixes two HIGH-severity CodeQL path traversal alerts (CWE-22) in
src/routes/reports.js:/downloadroute):path.join('/reports', filename)allowed user-supplied../sequences to escape the reports directory. Now usespath.resolve()to normalize the path and validates it stays within/reports./viewroute): User-suppliedreq.query.pathwas passed directly tofs.readFileSync, allowing arbitrary file reads. Now usespath.basename()to strip all directory components from the user input before constructing the file path, and adds rate limiting viaexpress-rate-limit.Updates since last revision
Addressed two additional CodeQL alerts raised against the first commit:
/viewroute): Replaced thepath.resolve + startsWithguard withpath.basename()sanitization. This completely strips directory components from the user input so the resolved path can never escape/reports. CodeQL did not recognize the previousstartsWithguard as sufficient forfs.readFileSync./viewroute): Addedexpress-rate-limitmiddleware (100 requests per 15-minute window) to the/viewhandler.pathquery parameters.express-rate-limitas a new dependency inpackage.json.Note: The
/downloadand/viewroutes now use different sanitization strategies —path.resolve + startsWithfor/download(which CodeQL accepted forres.sendFile) andpath.basenamefor/view.Review & Testing Checklist for Human
/viewnow only supports flat filenames:path.basename()strips all directory components, so requests likeGET /reports/view?path=subdir/report.pdfwill resolve to/reports/report.pdfinstead of/reports/subdir/report.pdf. Verify this restriction is acceptable for existing consumers./downloadroute input validation: The/downloadhandler does not validate thatreq.query.fileis present or a string.path.resolve('/reports', undefined)produces/reports/undefined. Consider whether a guard is needed here too.express-rate-limitdefault memory store is not shared across processes. If the app runs in a cluster, the rate limit is per-worker. Confirm this is acceptable or swap to a shared store (e.g.,rate-limit-redis).GET /reports/download?file=../../etc/passwd→ expect 400GET /reports/view?path=/etc/passwd→ expect the file/reports/passwd(basename strips the directory — verify this is the desired behavior, not a 400)GET /reports/view?path=../../etc/passwd→ expect the file/reports/passwdGET /reports/view(no path param) → expect 400/reports/→ expect successNotes
node -c).package-lock.jsonwas newly generated bynpm installwhen adding theexpress-rate-limitdependency.