Path Traversal in Express with Api Keys
Path Traversal in Express with Api Keys — how this specific combination creates or exposes the vulnerability
Path Traversal in an Express API protected only by API keys remains a classic web vulnerability despite the presence of authentication. An API key proves identity or subscription status, but it does not enforce authorization on filesystem operations. If an endpoint accepts user input—often a filename or path parameter—and uses it directly with Node.js utilities such as fs.readFile or path.join, an attacker can supply sequences like ../../../etc/passwd to traverse directory boundaries and read files outside the intended directory.
Consider an Express route that serves configuration files based on a keyed request without validating or sanitizing the path:
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
app.get('/config', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.VALID_API_KEY) {
return res.status(401).send('Unauthorized');
}
const filename = req.query.name;
const filePath = path.join('/opt/app/configs', filename);
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
app.listen(3000);
Here, the API key check passes, but the filename query parameter is used unsafely. An attacker can send ?name=../../../etc/passwd and, because the path is joined without canonicalization or strict allowlisting, the server may leak sensitive system files. This demonstrates that API keys protect access to the endpoint, not the correctness of the operation; Path Traversal persists because the application trusts user input for filesystem navigation.
In a black-box scan, middleBrick tests such endpoints by submitting traversal sequences and checking whether responses differ from expected behavior, indicating unauthorized file exposure. Even when API keys are required, findings may surface if the scanner can reach the endpoint with a valid key or if the key is inadvertently omitted in tests. The presence of an API key does not eliminate the risk; it only changes the attacker’s prerequisites.
Api Keys-Specific Remediation in Express — concrete code fixes
To remediate Path Traversal while using API keys in Express, you must treat API keys as identity proof and still enforce strict path controls. Do not rely on the key alone to prevent unsafe filesystem access. Apply defense in depth: validate and sanitize input, use allowlisting, and avoid direct filesystem paths derived from user data.
1. Use a strict allowlist of permitted filenames
Instead of constructing paths from raw input, map allowed logical names to real filenames:
const allowed = new Set(['app.yml', 'service.json', 'routes.yaml']);
app.get('/config', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.VALID_API_KEY) {
return res.status(401).send('Unauthorized');
}
const name = req.query.name;
if (!allowed.has(name)) {
return res.status(400).send('Invalid config name');
}
const filePath = path.join('/opt/app/configs', name);
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
2. Normalize and validate paths with path.resolve and a chroot-like base check
Resolve the final path and ensure it remains under the intended base directory:
app.get('/files', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.VALID_API_KEY) {
return res.status(401).send('Unauthorized');
}
const requested = path.resolve(req.query.file || '');
const base = path.resolve('/opt/app/uploads');
if (!requested.startsWith(base)) {
return res.status(403).send('Forbidden path');
}
fs.readFile(requested, 'utf8', (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
3. Use a hardened path library for complex routing
For more dynamic needs, consider a library approach that enforces canonical paths and prevents null-byte and encoding tricks:
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const safeJoin = (base, target) => {
const targetResolved = path.resolve(target);
const baseResolved = path.resolve(base);
return targetResolved.startsWith(baseResolved) ? targetResolved : null;
};
app.get('/docs', (req, res) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.VALID_API_KEY) {
return res.status(401).send('Unauthorized');
}
const safePath = safeJoin('/opt/app/public', req.query.path);
if (!safePath) return res.status(403).send('Invalid path');
fs.readFile(safePath, 'utf8', (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
These patterns ensure that even when API keys are valid, the application does not allow directory escapes. Combine this with runtime scans using middleBrick to detect Path Traversal and verify that your remediation behaves as expected under unauthenticated attack surface testing.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |