Clickjacking in Hapi with Mutual Tls
Clickjacking in Hapi with Mutual Tls — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an attacker tricks a user into clicking or interacting with a transparent or opaque layer over a trusted page. In Hapi, mutual Transport Layer Security (TLS) — where both client and server present certificates for authentication — protects the confidentiality and integrity of the request, but it does not prevent clickjacking. The confusion often arises because mutual TLS authenticates the client to the server, yet it does not enforce how the resource is rendered in the browser.
When a Hapi server uses mutual TLS, the server validates the client certificate during the TLS handshake and may establish a strong identity for the client. However, once the request completes and the server returns an HTML response, the browser renders it without any additional context that would prevent framing. If the response lacks anti-clickjacking headers such as X-Frame-Options or Content-Security-Policy with frame-ancestors, an attacker can embed the endpoint in an iframe on a malicious site, regardless of the mutual TLS state.
A concrete scenario: a Hapi service protected by mutual TLS exposes an internal dashboard at /admin. The server trusts client certificates and returns sensitive controls (e.g., buttons to rotate keys or change configurations). An attacker crafts a phishing page that loads https://api.example.com/admin inside an invisible iframe. Even though the browser sends the client certificate during the request (because the user has a valid certificate installed and the site prompts for it), the UI can be overlaid with fake elements, capturing clicks intended for the legitimate controls. Mutual TLS here ensures the request comes from a permitted client, but it does nothing to stop the UI from being hijacked.
The risk is compounded under certain deployment patterns. For example, if the Hapi server sets permissive Cross-Origin Resource Sharing (CORS) and lacks frame-ancestors restrictions, an attacker can combine cross-origin embedding with social engineering to amplify impact. Developers might mistakenly assume mutual TLS implies top-level navigation safety, but the browser’s same-origin policy and framing rules remain independent of the TLS layer. Therefore, in a middleBrick scan of a Hapi endpoint using mutual TLS, findings related to missing X-Frame-Options or weak Content-Security-Policy will highlight the absence of client-side framing controls, not the presence or absence of mutual TLS.
Mutual Tls-Specific Remediation in Hapi — concrete code fixes
To address clickjacking in a Hapi service that uses mutual TLS, apply standard anti-framing headers while preserving the mutual TLS setup. The server must explicitly deny framing via HTTP headers and ensure that sensitive routes do not rely on UI-only mitigations. Below are concrete code examples that combine mutual TLS configuration with clickjacking protections.
First, set security headers in Hapi to prevent embedding. Use helmet (which sets X-Frame-Options and Content-Security-Policy) and explicitly configure frame rules. For routes that require strict isolation, override or extend the CSP to include frame-ancestors 'none'.
const Hapi = require('@hapi/hapi');
const helmet = require('helmet');
const init = async () => {
const server = Hapi.server({
port: 443,
tls: {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: fs.readFileSync('ca-cert.pem'),
requestCert: true,
rejectUnauthorized: true
}
});
// Apply helmet with custom CSP to prevent framing
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (response.variety === 'view') {
response.header('X-Frame-Options', 'DENY');
response.header('Content-Security-Policy', "default-src 'self'; frame-ancestors 'none'");
}
return h.continue;
});
server.route({
method: 'GET',
path: '/admin',
options: {
auth: false // auth handled at TLS layer via client certs
},
handler: (request, h) => {
return { page: 'admin dashboard' };
}
});
await server.start();
};
init();
If you use route-specific rules, apply the headers conditionally. For example, for endpoints that render HTML and are sensitive to clickjacking, enforce a strict CSP. For API-only routes that return JSON, framing is irrelevant, but keeping X-Frame-Options consistent reduces misconfiguration risk.
Second, validate and document the mutual TLS expectations so developers understand that client certificates are verified at the TLS layer but do not affect browser framing. In the Hapi server setup, ensure requestCert and rejectUnauthorized are set appropriately. This guarantees that only clients with trusted certificates can establish a connection, while the anti-framing headers protect the UI surface.
Finally, test the configuration using a middleBrick scan. Submit the endpoint URL to verify that headers like X-Frame-Options and Content-Security-Policy are present and correctly restrict framing. The scan will surface missing controls even when mutual TLS is active, enabling teams to remediate clickjacking risks without altering the certificate validation logic.