Path Traversal in Hapi with Jwt Tokens
Path Traversal in Hapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Path Traversal occurs when user-controlled input is used to construct file system paths without proper validation, allowing an attacker to navigate outside intended directories using sequences like ../. In Hapi, this risk can become more nuanced when JWT tokens influence route handling, file resolution, or access-controlled resources. A common pattern is using a JWT claim (for example, a tenant identifier, user role, or file namespace) to decide which directory or file prefix the server should use when building a filesystem path.
If the JWT payload is trusted implicitly and concatenated into a path, an attacker who can influence or forge a token (via stolen secrets, weak signing algorithms, or token confusion) may cause the server to resolve unintended files. For example, a route like /documents/{file} might combine a decoded JWT field such as tenantId with the user-supplied filename to serve files from /data/{tenantId}/{file}. Without canonicalization and strict validation, an attacker supplying ../../etc/passwd as {file} could traverse outside the tenant directory.
Hapi does not inherently introduce path traversal; the risk arises from developer choices in route implementation and token usage. When JWT tokens are used to derive base directories or access rules, ensure the token’s claims are validated, scoped, and treated as untrusted input. Failing to resolve $ref dependencies in an OpenAPI spec or accepting unverified claims can expand the unauthenticated attack surface that middleBrick scans, potentially flagging insecure path construction patterns.
Consider an implementation that builds a filesystem path from a JWT claim and a user parameter:
const Hapi = require('@hapi/hapi');
const jwt = require('jsonwebtoken');
const path = require('path');
const fs = require('fs');
const server = Hapi.server({ port: 4000 });
server.route({
method: 'GET',
path: '/files/{filename}',
options: {
validate: {
headers: {
authorization: Joi.string().required()
}
},
handler: (request, h) => {
const authHeader = request.headers.authorization;
const token = authHeader.replace('Bearer ', '');
const decoded = jwt.verify(token, 'weak-secret'); // weak secret or no verification
const baseDir = `/data/${decoded.tenantId}`; // Jwt-derived base
const filePath = path.join(baseDir, request.params.filename);
return fs.readFileSync(filePath, 'utf8');
}
}
});
In this example, if the JWT’s tenantId is attacker-controlled (e.g., via token confusion or a weak signing key), and the filename is user-supplied without normalization, an attacker can traverse directories. Even with a seemingly safe tenant scope, inputs like ../../../etc/shadow can escape if path joins are not sanitized. middleBrick’s checks for Input Validation and Property Authorization can highlight such risky route and token usage patterns.
Another scenario involves serving user-uploaded assets where JWTs define a user identifier used as a folder name. If the server uses the JWT subject (sub) directly in the path, and also exposes a download endpoint that does not enforce strict access checks, an attacker who obtains or guesses another user’s token can traverse or access files belonging to other users (a BOLA/IDOR amplification). Proper claim validation and canonical path resolution mitigate this class of issue.
Jwt Tokens-Specific Remediation in Hapi — concrete code fixes
Remediation focuses on strict JWT validation, avoiding direct concatenation of claims into filesystem paths, and canonicalizing user input. Treat JWT claims as untrusted and enforce audience, issuer, and token binding where applicable.
1) Validate JWTs rigorously and avoid weak secrets:
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
audience: 'my-api',
issuer: 'https://auth.example.com',
algorithms: ['RS256']
});
2) Normalize and validate filenames before using them in paths. Use a whitelist of allowed characters and reject path separators:
const allowed = /^[a-zA-Z0-9._-]+$/;
if (!allowed.test(request.params.filename)) {
throw Boom.badRequest('Invalid filename');
}
const safeName = path.basename(request.params.filename);
const filePath = path.join(baseDir, safeName);
3) Avoid JWT-derived paths when possible; if necessary, scope strictly and canonicalize:
const baseDir = path.resolve('/data', decoded.tenantId);
if (!baseDir.startsWith(path.resolve('/data'))) {
throw Boom.forbidden('Access denied');
}
const filePath = path.join(baseDir, safeName);
4) Prefer a mapping from claims to directories using a server-side allowlist rather than raw claim values:
const tenantMap = {
acme: 'acme_corp',
> widget: 'widget_inc'
};
const tenantDir = tenantMap[decoded.tenant];
if (!tenantDir) throw Boom.unauthorized('Invalid tenant');
const filePath = path.join('/data', tenantDir, safeName);
These steps reduce the risk that JWT claims contribute to path traversal, aligning with strengthened input validation and property authorization checks that middleBrick evaluates.
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 |