HIGH cors wildcardrailsmutual tls

Cors Wildcard in Rails with Mutual Tls

Cors Wildcard in Rails with Mutual Tls — how this specific combination creates or exposes the vulnerability

A CORS wildcard origin (Access-Control-Allow-Origin: *) combined with Mutual TLS (mTLS) in a Ruby on Rails application can weaken the intended assurance of client authentication. mTLS requires the client to present a valid certificate during the TLS handshake, which proves possession of a private key. However, CORS is enforced by the browser after the TLS handshake completes. When an endpoint allows any origin via a wildcard, the browser will accept the response from a client that presented a valid mTLS certificate and then allow JavaScript from the permitted origins to read the response. This means the strong client authentication provided by mTLS can be undermined if the response is inadvertently exposed to browser scripts from unauthorized origins.

For example, consider a Rails API that uses config.api_only = true and a custom ApplicationController that sets CORS headers without origin validation. If the server also terminates mTLS at the load balancer or reverse proxy and maps the client certificate to a user identity, the backend may believe the request is authenticated. However, the browser will process the response for any origin allowed by the wildcard. An attacker who controls a page on a different origin could embed an image or script tag, or use fetch with mode: 'cors', and potentially read reflected sensitive information if the response contains data intended only for the mTLS-authenticated client. This violates the principle of same-origin policy layering that defense-in-depth relies on.

In practice, Rails applications often rely on the rack-cors gem to manage headers. A configuration that sets origins '*' while the server expects mTLS creates a mismatch: the server authenticates the client with certificates, but the browser treats the response as shareable across all origins. This is especially risky for endpoints that return sensitive data, such as those protected by OWASP API Top 10 Broken Access Control, because the browser may expose the data to malicious scripts. Even if the mTLS handshake succeeds, the CORS policy must be aligned with the intended audience to prevent unauthorized cross-origin access to authenticated responses.

Real-world attack patterns include credential or token leakage via cross-origin requests where the client certificate is presented but the origin policy is unrestricted. This does not mean mTLS is invalid; it means the application must ensure that CORS rules reflect the same trust boundaries. For endpoints requiring mTLS, origins should be explicitly restricted to known, trusted domains rather than using a wildcard. This preserves the integrity of certificate-based authentication while preventing browser-mediated data exposure.

Mutual Tls-Specific Remediation in Rails — concrete code fixes

To secure a Rails application using mTLS, you must enforce strict origin validation in CORS configuration and ensure the application correctly interprets client certificates. Below are concrete, syntactically correct examples that align CORS policy with mTLS authentication.

1. Configure CORS with specific origins

Replace wildcard origins with explicit domains. This ensures that only trusted frontends can access authenticated responses.

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'https://app.example.com', 'https://admin.example.com'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      expose: ['X-Request-Id', 'X-Duration'],
      max_age: 1728000
  end
end

2. Map client certificate to current user in a before action

Use the client certificate presented during mTLS to identify and authenticate the request. This keeps CORS and authentication consistent within Rails.

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  before_action :authenticate_client_from_cert

  private

  def authenticate_client_from_cert
    cert = request.env['SSL_CLIENT_CERT']
    if cert.present?
      x509 = OpenSSL::X509::Certificate.new(cert)
      # Example: map certificate serial to a user in your database
      user = User.find_by(certificate_serial: x509.serial.to_s)
      if user
        # Store user for authorization in downstream actions
        @current_user = user
      else
        render_unauthorized
      end
    else
      render_unauthorized
    end
  end

  def render_unauthorized
    render json: { error: 'Unauthorized' }, status: :unauthorized
  end
end

3. Enforce mTLS at the proxy or web server and pass identity to Rails

When terminating TLS at a load balancer or reverse proxy, configure it to verify client certificates and forward the identity to Rails via a trusted header. Ensure Rails only accepts this header from trusted proxies.

# config/puma.rb or equivalent server config
# Set trusted headers so Action Dispatch parses them safely
if ENV['TRUSTED_PROXY']
  Rails.application.config.action_dispatch.trusted_proxies = [IPAddr.new(ENV['TRUSTED_PROXY'])]
end
# Example header set by the proxy after mTLS verification
# X-SSL-Client-Subject: CN=alice,OU=Engineering,O=Example,C=US
# Rails can read this in a controller when the proxy is trusted
subject = request.headers['HTTP_X_SSL_CLIENT_SUBJECT']

4. Combine CORS and authentication checks in an API controller

For endpoints that require both CORS and mTLS, perform explicit checks and avoid relying solely on wildcard origins.

# app/controllers/api/v1/data_controller.rb
module Api::V1
  class DataController < ApplicationController
    before_action :authenticate_client_from_cert

    def show
      # Only allow requests from origins explicitly permitted by CORS
      unless allowed_origin?(request.headers['Origin'])
        render_unauthorized and return
      end

      # Proceed with data that is scoped to @current_user
      render json: { data: secure_data_for(@current_user) }
    end

    private

    def allowed_origin?(origin)
      allowed = ['https://app.example.com', 'https://admin.example.com']
      allowed.include?(origin)
    end
  end
end

These examples demonstrate how to align CORS policy with mTLS authentication in Rails. The key is to avoid wildcard origins when client certificates are used and to explicitly manage which origins can access authenticated responses. This reduces the risk of exposing mTLS-authenticated data to unauthorized browser origins.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Does using a CORS wildcard with mTLS automatically bypass client certificate validation in browsers?
No. mTLS certificate validation occurs at the TLS layer before the HTTP request reaches Rails. However, a wildcard CORS origin can allow browser scripts from untrusted origins to read responses that were authenticated via mTLS, effectively exposing data that should be restricted.
Should I disable CORS entirely if I use mTLS?
Not necessarily. CORS still matters because it controls which origins can make browser-based requests to your API. Even with mTLS, you should restrict origins to trusted frontends to prevent unauthorized cross-origin access to authenticated responses.