Cors Wildcard in Grape with Hmac Signatures
Cors Wildcard in Grape with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A Access-Control-Allow-Origin: * wildcard combined with HMAC signatures in a Grape API creates a dangerous mismatch between authentication and authorization. HMAC signatures typically rely on a shared secret to sign requests, and many implementations treat the signature as proof of integrity and origin. When allow_origin: '*' is set, the browser will accept responses for any origin, which can cause the signed response to be read by a malicious site if the request also includes credentials or sensitive data. The CORS spec prohibits credentials with a wildcard, but many clients and servers misconfigure this by allowing credentials while using the wildcard, or by not tightening the origin check after signature validation. This enables a scenario where an attacker can trick a victim’s browser into making a signed request (e.g., via an embedded image or script), and the response may be accessible to the attacker if CORS is too permissive, leading to data exposure or unauthorized actions being replayed.
In Grape, a common pattern is to verify the HMAC signature in an API helper, but if the CORS middleware sets allow_origin: '*' before the route runs, the browser may expose the response to a malicious page. Even if the server validates the signature correctly, the client-side JavaScript can read the response because the wildcard allows any origin. This is particularly risky for endpoints that return sensitive information or tokens. An attacker can craft a request that includes the victim’s authentication cookie (if sent automatically) and a valid HMAC (if the secret is leaked or predictable), and the wildcard CORS policy allows the attacker’s page to read the response. The combination therefore turns a correctly signed request into a cross-origin data leak.
Real-world findings from scanners highlight this pattern: endpoints with HMAC verification but a wildcard CORS policy receive a high severity score because the control boundary is effectively bypassed. The signature ensures the request has not been tampered with, but it does not prevent the response from being delivered to an unauthorized origin. This maps to OWASP API Top 10 controls related to broken object level authorization and excessive data exposure, and can intersect with SSRF if the wildcard is combined with unchecked redirect parameters. The risk is not theoretical; tools have demonstrated extraction of signed tokens via embedded malicious pages when CORS is misconfigured.
Hmac Signatures-Specific Remediation in Grape — concrete code fixes
To fix the issue, tighten CORS to specific origins and ensure HMAC verification occurs before any response is sent. Do not use * when credentials or sensitive data are involved. Instead, explicitly set allowed origins and mirror the Origin header when it matches your allowlist. Below is a complete Grape API example with HMAC verification and secure CORS settings.
require 'grape'
require 'openssl'
require 'base64'
class SecureApi < Grape::API
# Explicit allowlist — never use '*' when handling authenticated/signed responses
ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com'].freeze
before { verify_hmac_signature }
after { set_secure_cors_headers }
helpers do
def verify_hmac_signature
provided = request.env['HTTP_X_API_SIGNATURE']
timestamp = request.env['HTTP_X_API_TIMESTAMP']
return error!('Missing signature', 401) unless provided&.present? && timestamp&.present?
# Reject stale requests (replay protection)
within = 300 # 5 minutes
now = Time.now.to_i
return error!('Request expired', 401) if (now - timestamp.to_i).abs > within
body = request.body.read
request.body.rewind
secret = ENV.fetch('HMAC_SECRET') { raise 'HMAC secret missing' }
computed = OpenSSL::HMAC.hexdigest('sha256', secret, [timestamp, body].join('.'))
return error!('Invalid signature', 401) unless ActiveSupport::SecurityUtils.secure_compare(computed, provided)
# Optionally set an authenticated request flag for downstream routes
@api_auth = { verified: true, timestamp: timestamp }
end
def set_secure_cors_headers
origin = request.env['HTTP_ORIGIN']
if origin && ALLOWED_ORIGINS.include?(origin)
header['Access-Control-Allow-Origin'] = origin
header['Access-Control-Allow-Credentials'] = 'true'
else
header['Access-Control-Allow-Origin'] = ''
header['Access-Control-Allow-Credentials'] = 'false'
end
header['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
header['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, X-API-Signature, X-API-Timestamp'
header['Access-Control-Expose-Headers'] = 'X-Rate-Limit-Limit, X-Rate-Limit-Remaining'
header['Access-Control-Max-Age'] = '86400'
end
end
# Example protected endpoint
get '/resource' do
# At this point, HMAC is verified and origin is trusted
{ data: 'sensitive', verified_at: @api_auth[:timestamp] }
end
# Preflight handling
options '*' do
set_secure_cors_headers
200
end
end
Key points in the remediation:
- Replace
allow_origin: '*'with an explicit allowlist and mirror the Origin header only when it matches. - Always use
secure_compare(or equivalent constant-time comparison) to prevent timing attacks on the HMAC. - Include a timestamp and short window to prevent replay attacks; reject requests outside the window.
- Set
Access-Control-Allow-Credentialsonly when origin is trusted, and avoid sending it with a wildcard. - Expose only necessary headers and avoid exposing sensitive data to any origin.
For teams using the middleBrick ecosystem, the CLI can be integrated into CI to catch these misconfigurations before deployment: use middlebrick scan <url> to detect wildcard CORS with HMAC setups. If you need continuous oversight, the Pro plan adds scheduled scans and GitHub Action PR gates to fail builds when a high-severity CORS or signature validation issue is found. The Dashboard helps track these findings over time, and the MCP Server lets you scan APIs directly from your AI coding assistant while you develop.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |