-
Notifications
You must be signed in to change notification settings - Fork 23
Potential fix for code scanning alert no. 3: Uncontrolled data used in path expression #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,14 +12,17 @@ export default function handler(req, res) { | |||||
| return res.status(400).json({ error: 'Filename is required' }); | ||||||
| } | ||||||
|
|
||||||
| // VULNERABILITY: Path Traversal | ||||||
| // User input is used directly to construct file paths | ||||||
| // An attacker could use input like: "../../../../etc/passwd" | ||||||
| const filePath = path.join(process.cwd(), 'uploads', filename); | ||||||
|
|
||||||
| // Securely construct a path under the uploads directory | ||||||
| const uploadsRoot = path.join(process.cwd(), 'uploads'); | ||||||
| const resolvedPath = path.resolve(uploadsRoot, String(filename)); | ||||||
|
|
||||||
| // Ensure the resolved path is within the uploads root to prevent path traversal | ||||||
| if (!resolvedPath.startsWith(uploadsRoot + path.sep) && resolvedPath !== uploadsRoot) { | ||||||
|
||||||
| if (!resolvedPath.startsWith(uploadsRoot + path.sep) && resolvedPath !== uploadsRoot) { | |
| if (!resolvedPath.startsWith(uploadsRoot + path.sep)) { |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current validation doesn't protect against symlink attacks. If a symbolic link exists within the uploads directory that points to a file outside of it, the validation will pass but the file read will access the symlink target outside the allowed directory.
To fully prevent path traversal via symlinks, you should resolve the real path before validation. The PR description mentions this: use fs.realpathSync(resolvedPath) to resolve symlinks, then validate that the real path is still within uploadsRoot. Note that fs.realpathSync will throw an error if the file doesn't exist, so you'll need to handle that case appropriately (either with try-catch or by checking existence first).
See below for a potential fix:
try {
// Resolve symlinks and get the real filesystem path
const realPath = fs.realpathSync(resolvedPath);
// Ensure the real path is within the uploads root to prevent path traversal and symlink attacks
if (!realPath.startsWith(uploadsRoot + path.sep) && realPath !== uploadsRoot) {
return res.status(400).json({ error: 'Invalid filename' });
}
const fileContent = fs.readFileSync(realPath, 'utf8');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting
filenameto a string usingString(filename)is good for safety, but this should happen before the path resolution. Iffilenameisnullorundefined(which shouldn't happen due to the check on line 11, but could theoretically occur),String(null)becomes"null"andString(undefined)becomes"undefined", which would then be used as literal filename strings.While the current code has a guard on line 11 that checks
if (!filename), it's better to be defensive. Consider validating that filename is a non-empty string before proceeding with path operations.