Server Side Template Injection in Hapi with Jwt Tokens
Server Side Template Injection in Hapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in Hapi becomes higher risk when JWT tokens are used for session handling and route protection. Hapi applications often parse and validate JWTs in server-side templates or in request preprocessing steps before rendering views. If a template is constructed using unsanitized values extracted from the JWT payload—such as user roles, display names, or injected claims—an attacker who can influence the token content may achieve template injection.
Consider a scenario where a Hapi route reads a JWT, extracts a user-supplied claim, and passes it directly to a template engine without validation or escaping. For example, a route might use the payload’s name field to personalize a page. Because JWTs can be unsigned or forged when validation is misconfigured, an attacker can supply a malicious claim containing template syntax. When the server renders the template, the injected code can execute in the context of the application, leading to sensitive data exposure or remote code execution.
In a black-box scan, middleBrick tests unauthenticated endpoints and checks whether data from JWTs (e.g., via Authorization header parsing) influences template rendering. The scanner’s 12 security checks run in parallel and include Authentication, Input Validation, and Data Exposure, which are relevant when JWTs interact with server-side templates. If a template engine fails to escape user-controlled data originating from a JWT, middleBrick flags this as a high-severity finding with remediation guidance to treat all token-derived data as untrusted input.
SSRF and Unsafe Consumption checks also matter here because a malicious JWT claim could direct the server to load remote templates or external resources, amplifying the impact. Because JWT tokens often carry metadata and roles, developers may mistakenly trust them more than form inputs, increasing the likelihood of insecure template usage. middleBrick’s LLM/AI Security checks are unique in detecting whether prompts or configuration patterns in server-side logic expose token handling or template paths that could be abused.
To illustrate, a vulnerable Hapi route might look like this in JavaScript:
const Hapi = require('@hapi/hapi');
const Jwt = require('@hapi/jwt');
const vision = require('@hapi/vision');
const init = async () => {
const server = Hapi.server({ port: 4000 });
await server.register([Jwt, vision]);
server.views({
engines: { html: { modulePath: 'handlebars' } },
path: './views',
});
server.route({
method: 'GET',
path: '/profile',
options: {
auth: false,
handler: (request, h) => {
const token = request.headers.authorization?.split(' ')[1];
let payload = {};
if (token) {
try {
// Insecure: no validation or key management shown
payload = JwtUtils.decode(token); // hypothetical decoder
} catch (err) {}
}
// Vulnerable: injecting untrusted JWT claim into template
return h.view('profile', { username: payload.name || 'Guest' });
},
},
});
};Jwt Tokens-Specific Remediation in Hapi — concrete code fixes
Remediation focuses on strict JWT validation, avoiding direct use of token claims in templates, and ensuring all dynamic data is escaped. First, always validate the JWT using a trusted library with a verified signing key and expected audience and issuer. Never decode and trust the payload without verification.
Second, map only safe, non-user-controlled data from the token to the template context—such as user ID for lookup rather than display name. If you must pass data from the JWT to the UI, sanitize and escape it explicitly. For Handlebars templates used with @hapi/vision, enable autoescaping and avoid triple-stash syntax that disables escaping.
Below is a hardened example that validates a JWT and uses a database lookup instead of injecting claims directly into the template:
const Hapi = require('@hapi/hapi');
const Jwt = require('@hapi/jwt');
const vision = require('@hapi/vision');
const init = async () => {
const server = Hapi.server({ port: 4000 });
await server.register([Jwt, vision]);
server.views({
engines: { html: { modulePath: 'handlebars' } },
path: './views',
});
server.route({
method: 'GET',
path: '/profile',
options: {
auth: false,
handler: async (request, h) => {
const authHeader = request.headers.authorization;
let userId = null;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.split(' ')[1];
try {
// Secure: validate with key and options
const { isValid, credentials } = await Jwt.validate(token, 'your-secret-key', {
rules: { aud: 'your-audience', iss: 'your-issuer' },
});
if (isValid && credentials.userId) {
userId = credentials.userId;
}
} catch (err) {
// Log but do not expose details
console.error('JWT validation failed:', err);
}
}
// Avoid injecting raw token claims into the view model
const user = userId ? await db.getUserById(userId) : null;
return h.view('profile', { user: user ? { name: escapeHtml(user.name) } : { name: 'Guest' } });
},
},
});
};
function escapeHtml(str) {
return str.replace(/[<>"']/g, (s) => ({'&': '&', '<': '<', '>': '>', '"': '"', \"'\": '''}[s]));
}
Additionally, configure your JWT library to reject unsigned tokens and enforce algorithm restrictions to prevent token substitution attacks. middleBrick’s Authentication and Input Validation checks can verify that your endpoints enforce these controls and that templates do not reflect untrusted data from JWTs without escaping.