Clickjacking in Sinatra with Api Keys
Clickjacking in Sinatra with Api Keys — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redressing attack where an attacker tricks a user into interacting with a hidden or disguised element inside an embedded frame. In Sinatra applications that use API keys for authentication, embedding UI actions inside an <iframe> can expose key-sensitive operations to clickjacking. For example, a helper method that builds an authorization URL with an API key query parameter may be invoked from a view that is served inside an <iframe> by a malicious site. If the application does not enforce frame-ancestor policies or use anti-CSRF tokens, a user logged into the app may unknowingly trigger state-changing requests that include their API key context, effectively performing actions on behalf of the attacker.
Consider a Sinatra route that renders a page containing an embedded third-party dashboard via an <iframe>:
require 'sinatra'
get '/dashboard' do
api_key = ENV['EXTERNAL_API_KEY']
# This page is intentionally embeddable (missing X-Frame-Options)
erb :dashboard, locals: { api_key: api_key }
end
An attacker can host a page with:
<iframe src="https://your-sinatra-app.com/dashboard" style="opacity:0;position:absolute"></iframe>
<button onclick="document.querySelector('iframe').contentWindow.postMessage(...)">Claim Reward</button>
If the dashboard performs key-bearing actions (e.g., calling an external API using the embedded API key) without frame-busting or anti-clickjacking protections, the user may be induced to click the visible button while the hidden iframe triggers authenticated calls. This becomes more impactful when API keys are passed in URLs or headers that are exposed to client-side code, as the browser automatically includes cookies and authorization headers in the embedded request, allowing the attacker to leverage the victim’s authenticated context.
Additionally, if the Sinatra app exposes an unauthenticated endpoint that returns sensitive data or triggers operations protected only by API keys in headers, and that endpoint is loaded inside an attacker-controlled frame, the app effectively exposes its key-bound operations to clickjacking. The risk is elevated when API keys are used as bearer tokens in headers (e.g., Authorization: ApiKey <key>) and the application lacks a strong Content Security Policy (CSP) with a restrictive frame-ancestors directive.
Api Keys-Specific Remediation in Sinatra — concrete code fixes
Remediation focuses on preventing embedding and ensuring API keys are not exposed to clickjacking-assisted interactions. Use HTTP headers to disallow framing and enforce strict CSP. Avoid embedding pages that carry API keys in iframes, and do not pass API keys in URLs that may be exposed to client-side JavaScript.
1) Set X-Frame-Options and Content-Security-Policy headers in Sinatra to block embedding:
require 'sinatra'
before do
# Prevent framing by any domain
headers 'X-Frame-Options' => 'DENY'
# Allow framing only by trusted domains (or omit 'allow-from' for full deny)
headers 'Content-Security-Policy' => "frame-ancestors 'self'; default-src 'self'"
end
get '/dashboard' do
api_key = ENV['EXTERNAL_API_KEY']
erb :dashboard, locals: { api_key: api_key }
end
2) Avoid exposing API keys in views or client-side JavaScript. Instead, proxy sensitive actions through server-side endpoints that use stored keys securely:
require 'sinatra'
require 'net/http'
require 'json'
post '/api/proxy/charge' do
# Server-side key usage — key never reaches the browser
api_key = ENV['EXTERNAL_API_KEY']
uri = URI('https://external.example.com/charge')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "ApiKey #{api_key}"
request['Content-Type'] = 'application/json'
request.body = { amount: params[:amount], currency: params[:currency] }.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
status response.code.to_i
body response.body
end
3) If you must render API-related UI, ensure the page is not embeddable and include anti-CSRF tokens for any state-changing operations. Do not place API keys in query strings that could be leaked in logs or Referer headers:
get '/settings' do
# Render a form that requires a server-side CSRF token; do not embed raw API keys
erb :settings
end
4) For SPAs or external consumers, use short-lived tokens or session-bound credentials rather than long-lived API keys where possible, and enforce strict CSP to limit script sources.