HIGH broken access controlhanamimutual tls

Broken Access Control in Hanami with Mutual Tls

Broken Access Control in Hanami with Mutual Tls — how this specific combination creates or exposes the vulnerability

Broken Access Control (BAC) in Hanami when combined with Mutual Transport Layer Security (mTLS) can allow authenticated users to access or modify resources they should not reach. mTLS authenticates both client and server, but it does not enforce authorization. If Hanami routes and policies rely only on the presence of a valid client certificate without checking scopes, roles, or tenant boundaries, BOLA/IDOR and BFLA patterns emerge.

Consider a Hanami API where an endpoint is protected by mTLS: a client presents a valid certificate, Hanami verifies the certificate chain, and the request proceeds. If the route uses the certificate’s subject or a static identifier (e.g., org_id) to scope data, an attacker with a legitimate certificate can manipulate IDs or parameters to access another org’s records. Because mTLS terminates authentication early, developers may mistakenly skip per-request authorization checks, leading to unauthenticated-appearing access for valid certificate holders performing unauthorized actions.

In a real-world configuration, Hanami might load the client identity from the certificate and skip role-based checks under the assumption that mTLS is sufficient. This can cause Property Authorization failures: endpoints returning lists of resources might expose fields or entire subcollections that should be restricted by user role. For example, an admin-only endpoint that returns billing details could be invoked by a user with a valid client cert but without an admin role, leaking sensitive data.

Input validation interacts with BAC by allowing tampering of identifiers (e.g., :id) before Hanami’s authorization logic runs. If the app does not validate that the requesting principal owns the referenced resource, an attacker can iterate IDs or exploit weak indirect references. SSRF and unsafe consumption concerns are less direct but can appear if mTLS client certificates are mishandled in forwarded requests, causing the server to make unintended internal calls.

Because mTLS provides strong authentication, developers may overlook complementary controls like scope validation, tenant isolation, and least-privilege role mapping. middleBrick’s 12 security checks, including BOLA/IDOR and Property Authorization, are designed to surface these gaps by correlating authentication signals from mTLS with runtime authorization behavior, ensuring that verified identity maps correctly to allowed actions.

Mutual Tls-Specific Remediation in Hanami — concrete code fixes

To fix Broken Access Control when mTLS is in use, enforce explicit authorization after mTLS authentication and avoid using certificate metadata as the sole authorization source. Below are concrete Hanami examples.

1. Enforce scope and role checks after mTLS verification

Do not assume a valid client certificate implies the right permissions. Use a policy object that reads the certificate’s CN or SAN, maps it to a user/role, and checks scopes.

module Web::Controllers::Accounts
  class Show
    include Web::Action

    # After mTLS handshake, Hanami receives the client certificate via Rack environment
    def call(params)
      client_cert = env["ssl_client_cert"]
      return halt 401, { error: "missing client certificate" } unless client_cert

      identity = extract_identity(client_cert)
      # Explicit authorization: ensure identity has required scope/role
      if Authorizer.new(identity).can?(:read, :account)
        account = AccountRepository.new.find(params[:id])
        self.body = { account: AccountSerializer.new(account).serializable_hash }
      else
        halt 403, { error: "insufficient_scope" }
      end
    end

    private

    def extract_identity(cert)
      # Parse subject or SAN to obtain a user ID or org membership
      subject = cert.subject.to_s
      cn = subject[/CN=([^,]+)/, 1]
      org = subject[/O=([^,]+)/, 1]
      { subject: cn, org: org }
    end
  end
end

2. Use per-request authorization with data ownership

When exposing resources, validate that the authenticated principal owns or is permitted to access the resource, preventing BOLA/IDOR.

module Web::Controllers::Records
  class Update
    include Web::Action

    def call(params)
      client_cert = env["ssl_client_cert"]
      identity = extract_identity(client_cert)
      record_id = params[:id]

      record = RecordRepository.new.find_by_id(record_id)
      halt 404, { error: "not_found" } unless record

      # Ensure the record belongs to the org the identity is allowed to manage
      if record.org_id == identity[:org] && Authorizer.new(identity).can?(:update, record)
        RecordUpdater.new(record, params).call
        self.body = { status: "ok" }
      else
        halt 403, { error: "forbidden" }
      end
    end

    private

    def extract_identity(cert)
      # Same helper as above
      subject = cert.subject.to_s
      org = subject[/O=([^,]+)/, 1]
      { org: org }
    end
  end
end

3. Centralize authorization logic and map to compliance frameworks

Define policies that reference roles and scopes extracted from mTLS, and align them with OWASP API Top 10 and relevant compliance mappings.

# app/policies/authorizer.rb
class Authorizer
  def initialize(identity)
    @identity = identity
  end

  def can?(action, resource)
    role = RoleResolver.new(@identity).call
    PolicyEngine.new(role: role, scope: @identity[:org]).allowed?(action, resource)
  end
end

# config/policies.rb — mapping to compliance controls
{ 
  read_account: { compliance: ["PCI-DSS", "GDPR"] },
  write_account: { compliance: ["SOC2"] }
}

4. Validate and sanitize input before authorization

Ensure IDs and filters cannot be tampered to bypass ownership checks.

module Web::Validators
  class RecordId
    include Hanami::Validations::Form

    validations do
      required(:id).filled(:str?, format?: /\\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\z/)
    end
  end
end

5. Use middleware to normalize mTLS identity for Hanami apps

Normalize the certificate into a stable identity that your policies consume, avoiding ad-hoc parsing in every controller.

# lib/middleware/mtls_identity.rb
class MtlsIdentity
  def initialize(app)
    @app = app
  end

  def call(env)
    cert = env["ssl_client_cert"]
    if cert
      subject = cert.subject.to_s
      org = subject[/O=([^,]+)/, 1]
      user_id = subject[/CN=([^,]+)/, 1]
      env["identity"] = { org: org, user_id: user_id }
    else
      env["identity"] = nil
    end
    @app.call(env)
  end
end

# config/initializers/mtls.rb
Rack::Builder.new do
  use MtlsIdentity
  run Hanami.app
end

Frequently Asked Questions

Does mTLS alone prevent Broken Access Control in Hanami?
No. Mutual TLS authenticates the client but does not enforce authorization. Without explicit scope and role checks, valid certificates can still be used to access unauthorized resources (BAC/BOLA/IDOR). Always add per-request authorization after mTLS authentication.
How can I test for BAC with mTLS using middleBrick?
Use middleBrick to scan the endpoint with mTLS presented. The scan correlates the authenticated identity from the client certificate with runtime authorization behavior, surfacing BOLA/IDOR and Property Authorization findings with remediation guidance mapped to OWASP API Top 10 and compliance frameworks.