You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
View.prototype.lookup() in lib/view.js:104-123 uses path.resolve(root, name) without validating that the resolved path stays within the configured views directory. Combined with decodeURIComponent() in route parameter decoding, applications that pass user input to res.render() are exposed to path traversal.
res.sendFile() has path traversal protection via the send library's root containment check. The view system has no equivalent protection.
This is not a critical vulnerability since it requires the developer to pass unsanitized user input to res.render(). However, given that res.sendFile() already has containment via the send library, adding equivalent protection in View.prototype.lookup() would provide defense-in-depth consistency.
This is a known behaviour (#4445), but I agree that changing this is a good idea. I can't think of a reason why anyone would try to render something outside of the views directory, but that doesn't mean that nobody does this.
This probably requires a major release (6.x?) and documentation changes.
After reviewing lib/view.js, the res.render() docs, and prior discussion in #4445, this looks like working as designed rather than a new security bug.
View.prototype.lookup() resolves from the configured views path but does not enforce containment, and res.render() intentionally supports absolute view paths.
This behavior is covered by tests (test/res.render.js and test/app.render.js include absolute path rendering).
The API docs already warn that the view argument should not contain end-user input because it performs filesystem operations.
res.sendFile() is different by design: it uses send, which applies traversal protections (especially when root is set) because that API is commonly used with user-influenced file paths.
I’d triage this as a security hardening enhancement (defense in depth), not a vulnerability/regression. A default containment change would likely be breaking, so 6.x (or an opt-in strict mode in 5.x) plus docs updates seems like the safest path.
Path Containment Check for View.prototype.lookup()
Summary: The current implementation lacks path containment validation, which could allow path traversal attacks when resolving view templates.
Recommendation: Add a root containment check using path.relative() to ensure the resolved path stays within the root directory:
`javascript
// In lib/view.js around line 104-123
View.prototype.lookup = function lookup(name) {
var opts = this.options;
var root = opts.root;
var loc = opts.dirname;
var paths = [loc];
// ... existing path resolution logic ...
// ADD THIS CONTAINMENT CHECK:
var resolvedPath = path.resolve(root, filePath);
var relativePath = path.relative(root, resolvedPath);
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
throw new Error('View path traversal detected: ' + filePath);
}
return resolvedPath;
};
`
Reference: The send library implements similar protection via send.mime.lookup() path sanitization. The vulnerability exists because
ame can contain ../ sequences that escape the intended root directory, allowing arbitrary file reads.
Activity
fix: add path containment check in View.prototype.lookup()
fix: add path containment check in View.prototype.lookup()
som14062005 commentedon Mar 31, 2026
I've submitted a fix for this in PR #7142. Happy to make any changes
if the approach needs adjustment.
fix: add path containment check in View.prototype.lookup()
krzysdz commentedon Mar 31, 2026
This is a known behaviour (#4445), but I agree that changing this is a good idea. I can't think of a reason why anyone would try to render something outside of the views directory, but that doesn't mean that nobody does this.
This probably requires a major release (6.x?) and documentation changes.
som14062005 commentedon Mar 31, 2026
Thanks for the context on #4445 i understand this could be a
breaking change for users rendering files outside the views directory.
happy to:
Please let me know how you'd like to proceed.
IamLizu commentedon Apr 4, 2026
Thanks for the detailed report.
After reviewing
lib/view.js, theres.render()docs, and prior discussion in #4445, this looks like working as designed rather than a new security bug.View.prototype.lookup()resolves from the configuredviewspath but does not enforce containment, andres.render()intentionally supports absolute view paths.test/res.render.jsandtest/app.render.jsinclude absolute path rendering).viewargument should not contain end-user input because it performs filesystem operations.res.sendFile()is different by design: it usessend, which applies traversal protections (especially whenrootis set) because that API is commonly used with user-influenced file paths.I’d triage this as a security hardening enhancement (defense in depth), not a vulnerability/regression. A default containment change would likely be breaking, so 6.x (or an opt-in strict mode in 5.x) plus docs updates seems like the safest path.
janderik commentedon Jul 22, 2026
Path Containment Check for View.prototype.lookup()
Summary: The current implementation lacks path containment validation, which could allow path traversal attacks when resolving view templates.
Recommendation: Add a root containment check using path.relative() to ensure the resolved path stays within the root directory:
`javascript
// In lib/view.js around line 104-123
View.prototype.lookup = function lookup(name) {
var opts = this.options;
var root = opts.root;
var loc = opts.dirname;
var paths = [loc];
// ... existing path resolution logic ...
// ADD THIS CONTAINMENT CHECK:
var resolvedPath = path.resolve(root, filePath);
var relativePath = path.relative(root, resolvedPath);
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
throw new Error('View path traversal detected: ' + filePath);
}
return resolvedPath;
};
`
Reference: The send library implements similar protection via send.mime.lookup() path sanitization. The vulnerability exists because
ame can contain ../ sequences that escape the intended root directory, allowing arbitrary file reads.