Cross Site Request Forgery in Sinatra with Api Keys
Cross Site Request Forgery in Sinatra with Api Keys — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) in Sinatra when using API keys centers on the mismatch between authentication and authorization boundaries. API keys are typically passed in HTTP headers (e.g., x-api-key) and are often treated as a sufficient proof of identity for state-changing endpoints. However, headers like x-api-key are not automatically included in browser-originated requests such as form submissions or img tags, but they can be exposed in logs, browser developer tools, or via insecure referrer leakage. The vulnerability arises when a Sinatra application accepts an API key in a header for a state-changing route (POST/PUT/DELETE) but does not enforce additional same-origin or anti-CSRF protections, allowing an attacker to trick a victim with the valid key into executing unintended actions.
Consider a Sinatra endpoint that transfers funds using an API key passed in a header:
# vulnerable example
post '/transfer' do
api_key = request.env['HTTP_X_API_KEY']
if valid_api_key?(api_key)
# perform transfer using params
amount = params['amount']
account = params['account']
# ... transfer logic
else
halt 401, 'Invalid key'
end
end
Because the API key is in a header, a browser will not include it automatically in a forged <form> or <script>. However, if the API key is accidentally exposed—embedded in JavaScript, leaked in logs, or reused in a context where the browser does send headers (e.g., via an XMLHttpRequest from a compromised page)—an attacker can craft a request that the victim’s browser will submit with the key. Additionally, if the Sinatra app is also served with cookies for session-like behavior, and the API key is accepted alongside cookies, the boundary between authentication mechanisms blurs, increasing the attack surface. A common real-world pattern is using API keys for server-to-server flows while also allowing browser clients to send the same key via custom headers; if CORS is misconfigured to allow credentials or overly broad origins, an attacker can leverage JavaScript to make authenticated requests, effectively bypassing intended CSRF protections.
Another exposure vector is the use of unsafe HTTP methods or missing validation on the server. For instance, if a GET endpoint performs a state change (violating idempotency expectations) and relies solely on an API key in a header, an attacker can use a simple <img src="https://api.example.com/transfer?amount=1000&account=attacker" /> to induce a request. Although the browser won’t include the custom header, any intermediary that caches or logs may expose the key, or a malicious browser extension can read and reuse it. This is especially risky if the API key is static and long-lived, resembling a bearer token.
SSRF compounds the issue when internal Sinatra services accept API keys from incoming requests and then use those keys to call other internal APIs. An attacker who can induce the server to make authenticated requests internally may pivot within the network, leveraging API keys that were never intended for external exposure. The key takeaway is that API keys alone do not prevent CSRF when the request context can be influenced by an attacker; they authenticate, but they do not bind the request to a browser origin or a user’s session in a way that prevents forged submissions.
Api Keys-Specific Remediation in Sinatra — concrete code fixes
Remediation focuses on ensuring API keys are handled in a way that does not inadvertently enable CSRF while preserving their intended use for authentication. First, avoid using API keys for browser-based clients where CSRF is a concern; instead, use cookies with SameSite=Strict or Lax and anti-CSRF tokens for state-changing operations. For server-to-server flows, continue using API keys but enforce strict referrer and origin checks, and avoid mixing authentication mechanisms.
Example of a safer Sinatra route that validates an API key and rejects requests with potential CSRF indicators:
# safer example with origin/referrer checks
helpers do
def valid_api_key?(key)
# compare securely, e.g., ActiveSupport::SecurityUtils.secure_compare
key == ENV['EXPECTED_API_KEY']
end
def csrf_safe_request?
# For state-changing methods, require a custom header like X-Requested-With
# or check Origin/Referer to ensure same-site expectations.
%w[POST PUT DELETE].include?(request.request_method) ?
(request.env['HTTP_ORIGIN'] == 'https://trusted.example.com' ||
request.env['HTTP_REFERER']&.start_with?('https://trusted.example.com') ||
request.env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') : true
end
end
post '/transfer' do
api_key = request.env['HTTP_X_API_KEY']
halt 403, 'Missing key' unless api_key
halt 403, 'Invalid key' unless valid_api_key?(api_key)
halt 403, 'CSRF risk' unless csrf_safe_request?
amount = params['amount']
account = params['account']
# ... perform transfer
{ status: 'ok' }.to_json
end
Use explicit allowlists for origins and require a custom header or same-site context for any endpoint that accepts API keys and modifies state. If you must support browser clients, issue a short-lived token after key validation and require that token in a cookie with SameSite=Strict, paired with a CSRF token in forms or headers.
Code example of key validation with strict origin enforcement and secure comparison:
# strict validation with secure compare and origin enforcement
post '/webhook' do
provided = request.env['HTTP_X_API_KEY']
expected = ENV.fetch('WEBHOOK_API_KEY')
# Use secure compare to avoid timing attacks
unless provided && ActiveSupport::SecurityUtils.secure_compare(provided, expected)
halt 401, 'Invalid key'
end
# Ensure the request originates from the expected source
origin = request.env['HTTP_ORIGIN']
referer = request.env['HTTP_REFERER']
halt 403, 'Origin not allowed' unless origin == 'https://partner.example.com'
# optionally also check referer
# process webhook payload
status 200
body 'received'
end
Finally, rotate keys regularly, restrict key scope to least privilege, and monitor for anomalous usage patterns. These practices reduce the risk of CSRF-like abuse when API keys are part of the authentication strategy.