Description
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.
Reproduction
const express = require('express');
const app = express();
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/page/:name', (req, res) => res.render(req.params.name));
app.listen(3000);
curl http://localhost:3000/page/..%2f..%2f..%2fetc%2fpasswd
Route param decodes to ../../../../etc/passwd. View lookup resolves outside views directory.
Runtime verification on Express 5.2.1:
layer.match('/page/..%2f..%2fetc%2fpasswd')
// => true, params.name = "../../etc/passwd"
Context
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.
Suggested improvement
// lib/view.js - View.prototype.lookup()
var loc = resolve(root, name);
if (!loc.startsWith(resolve(root) + sep)) continue;
Description
View.prototype.lookup()inlib/view.js:104-123usespath.resolve(root, name)without validating that the resolved path stays within the configured views directory. Combined withdecodeURIComponent()in route parameter decoding, applications that pass user input tores.render()are exposed to path traversal.res.sendFile()has path traversal protection via thesendlibrary's root containment check. The view system has no equivalent protection.Reproduction
Route param decodes to
../../../../etc/passwd. View lookup resolves outside views directory.Runtime verification on Express 5.2.1:
Context
This is not a critical vulnerability since it requires the developer to pass unsanitized user input to
res.render(). However, given thatres.sendFile()already has containment via thesendlibrary, adding equivalent protection inView.prototype.lookup()would provide defense-in-depth consistency.Suggested improvement