Clickjacking in Hanami with Mutual Tls
Clickjacking in Hanami 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 interacting with a hidden or disguised element inside an invisible or misaligned frame. Hanami, a Ruby web framework, does not set default anti-clickjacking protections such as X-Frame-Options or Content-Security-Policy: frame-ancestors, so responses can be embedded by external pages unless explicitly guarded. Mutual Transport Layer Security (Mutual TLS), which requires both the client and the server to present valid certificates during the TLS handshake, is typically used to authenticate clients and enforce strict channel security. While Mutual TLS ensures that only authorized clients can establish a connection and that traffic is encrypted, it does not prevent the server’s responses from being framed by an attacker in a different origin. In other words, Mutual TLS secures the channel and access control at the network layer, but it does not enforce UI-level constraints. A common misconfiguration is to assume that strong transport security alone prevents clickjacking. This assumption can expose Hanami applications when they are served behind a reverse proxy or load balancer that terminates Mutual TLS and forwards requests to the Hanami app over plain HTTP internally. If the Hanami app does not generate frame-prevention headers, the UI remains embeddable regardless of how strong the Mutual TLS binding is. Another specific scenario involves embedded dashboards or administrative panels that are accessible to authenticated users. If these pages are reachable from an origin that also serves public content, an attacker can craft a page that loads the Hanami endpoint in a tiny, layered iframe, aligning UI elements (like buttons or forms) beneath the attacker’s visible controls. Because Mutual TLS does not restrict framing, the browser will render the embedded content and process user interactions as intended by the attacker. MiddleBrick’s unauthenticated scan checks whether frame-prevention headers and CSP frame-ancestors are present; without these controls, the UI surface remains vulnerable even when Mutual TLS is enforced. Developers might also inadvertently weaken protections by allowing cross-origin resource inclusion or by embedding pages in iframes intentionally without considering the broader attack surface. This combination of a UI-focused flaw and a transport-layer control illustrates why layered defenses are essential. Relying solely on Mutual TLS leaves clickjacking risks unaddressed in Hanami, and security testing should include header validation and CSP configuration as part of the 12 parallel checks.
Mutual Tls-Specific Remediation in Hanami — concrete code fixes
Remediation for clickjacking in Hanami must focus on HTTP headers and CSP, independent of Mutual TLS configuration. The primary defenses are X-Frame-Options and Content-Security-Policy with frame-ancestors. Below are concrete, working examples of how to enforce these headers in a Hanami application, alongside a sample Mutual TLS setup for the server.
Hanami middleware to set security headers
In a Hanami application, you can add a middleware that injects security headers for every response. Create a file such as lib/my_app/middleware/security_headers.rb:
module MyApp
module Middleware
class SecurityHeaders
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers['X-Frame-Options'] = 'DENY'
headers['Content-Security-Policy'] = "frame-ancestors 'none';"
[status, headers, body]
end
end
end
end
Then insert this middleware into the pipeline in config/application.rb:
module MyApp
class Application < Hanami::Application
use MyApp::Middleware::SecurityHeaders
# other middleware and configuration
end
end
Alternatively, if you prefer to configure headers per route or resource, you can use a before action in your controllers:
class Web::Controllers::Dashboards
include Web::Action
before do
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Content-Security-Policy'] = "frame-ancestors 'none';"
end
def show
# action logic
end
end
Mutual TLS server configuration example
Mutual TLS must be configured at the web server or reverse proxy layer that terminates TLS. Below is a representative snippet for a typical Ruby-compatible server (e.g., Puma with SSL) or a load balancer configuration that requires client certificates. This is not Hanami-specific but shows how to enforce client certificate verification.
# Example Puma config (config/puma.rb)
ssl_bind '0.0.0.0', '8443', {
cert: 'path/to/server.crt',
key: 'path/to/server.key',
verify_mode: 'verify_peer',
ca_file: 'path/to/ca_bundle.crt'
}
The verify_mode: 'verify_peer' ensures that clients must present a valid certificate signed by the trusted CA defined in ca_bundle.crt. This enforces Mutual TLS at the transport layer. Remember that after Mutual TLS termination, internal communication to Hanami may be over HTTP, so the security headers must be present on the Hanami responses to protect against clickjacking regardless of the transport security.
Complementary checks
When testing with tools like MiddleBrick, verify that both the security headers and Mutual TLS are in place. MiddleBrick’s scan will flag missing frame-ancestors or X-Frame-Options and will also surface unauthenticated LLM endpoints or other findings. Even with Mutual TLS, missing headers should be prioritized for remediation.