HIGH broken access controlhanamihmac signatures

Broken Access Control in Hanami with Hmac Signatures

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

Broken Access Control occurs when authorization checks are missing, incomplete, or bypassed, allowing attackers to access resources or perform actions they should not. In Hanami, a Ruby web framework that encourages explicit routing and controller actions, this often manifests when developer-written authorization logic is inconsistent or applied only to some entry points. When Hmac Signatures are used for request authentication—commonly to verify that a webhook or external integration originates from a trusted source—developers may incorrectly assume that signature validation alone is sufficient for authorization.

Hmac Signatures prevent tampering by ensuring request integrity, but they do not inherently enforce scope-based permissions or resource ownership. For example, an endpoint that accepts a valid Hmac Signature might still allow an authenticated integration to perform actions on any record by simply guessing or enumerating identifiers (IDs). If the Hanami application does not also validate that the authenticated integration is permitted to access the specific resource (e.g., a project, user, or order), this is a classic Broken Access Control vulnerability (related to OWASP API Top 10 #1, IDOR). Attackers can leverage weak or missing authorization checks to read, modify, or delete data across tenants or privilege boundaries, even when the Hmac Signature itself is valid.

Consider a Hanami endpoint that updates a subscription using a merchant webhook. The route may verify an Hmac Signature to confirm the webhook originates from the payment provider, but if the controller action uses a global scope or omits a policy check that the authenticated webhook is authorized for the specific subscription_id, an attacker who knows or guesses a valid subscription ID can trigger updates for any subscription. Because the signature validates origin, not intent or scope, the request passes security and proceeds with unintended side effects. This becomes especially dangerous when the endpoint performs state changes (PUT/PATCH/DELETE) rather than safe reads. The risk is compounded if logs or error messages inadvertently expose internal identifiers or stack traces, aiding further reconnaissance.

In API security scanning, this pattern is flagged as BOLA/IDOR and BFLA/Privilege Escalation when endpoints lack proper instance-level checks. middleBrick detects these gaps by correlating OpenAPI/Swagger definitions with runtime behavior across the unauthenticated attack surface. For Hanami applications, remediation requires pairing Hmac Signature validation with explicit authorization checks per resource, ensuring that scope and ownership are verified for every request, not just the initial signature.

Hmac Signatures-Specific Remediation in Hanami — concrete code fixes

To fix Broken Access Control when using Hmac Signatures in Hanami, you must enforce resource-level authorization after signature verification. Below are two concrete examples: one for a webhook endpoint and one for an internal API client, both using Hmac Signatures with Hanami’s built-in utilities.

Example 1: Webhook endpoint with Hmac Signature and resource ownership check

require "hanami/controller"
require "openssl"

class Webhooks::SubscriptionsController
  include Hanami::Controller

  before :validate_hmac_signature

  def update
    subscription_id = params.fetch("id")
    # After signature validation, enforce that the webhook is authorized for this subscription
    subscription = SubscriptionRepository.new.find(subscription_id)
    if subscription.nil? || subscription.account_id != current_account.id
      halt 403, { error: "forbidden" }.to_json
    end

    # Proceed with update logic
    # ...
  end

  private

  def validate_hmac_signature
    provided = request.env["HTTP_X_HMAC_SIGNATURE"]
    timestamp = request.env["HTTP_X_TIMESTAMP"]
    body = request.body.read
    secret = ENV.fetch("HMAC_WEBHOOK_SECRET")

    # Reconstruct the signed payload as the provider expects
    message = "#{timestamp}.#{body}"
    computed = OpenSSL::HMAC.hexdigest("sha256", secret, message)

    unless secure_compare(computed, provided)
      halt 401, { error: "invalid signature" }.to_json
    end

    # Optional: reject stale requests
    if (Time.now.to_i - Integer(timestamp)) > 300
      halt 400, { error: "stale request" }.to_json
    end
  end

  def secure_compare(a, b)
    return false unless a.bytesize == b.bytesize
    l = a.unpack "C#{a.bytesize}"
    res = 0
    b.each_byte { |byte| res |= byte ^ l.shift }
    res == 0
  end

  def current_account
    # Derive or fetch the account associated with this webhook, e.g., from a mapping keyed by a shared secret or issuer
    @current_account ||= AccountRepository.new.find_by_hmac_issuer(request.env["HTTP_X_ISSUER"])
  end
end

Example 2: Internal API client using Hmac Signatures with scoped tokens

require "hanami/controller"
require "openssl"

class Api::V1::ReportsController
  include Hanami::Controller

  before :authenticate_hmac

  def show
    report_id = params.fetch("id")
    # Ensure the scoped token allows access to this report
    report = ReportRepository.new.find(report_id)
    unless report && policy_scope.report_visible?(current_user, report)
      halt 403, { error: "forbidden" }.to_json
    end

    # Return report data
  end

  private

  def authenticate_hmac
    token = request.env["HTTP_AUTHORIZATION"]&.split(" ")&.last
    secret = ENV.fetch("HMAC_API_SECRET")
    timestamp = request.env["HTTP_X_TIMESTAMP"]
    path = request.path
    method = request.request_method
    body = request.body.read

    message = "#{timestamp}#{method}#{path}#{body}"
    expected = OpenSSL::HMAC.hexdigest("sha256", secret, message)

    unless token && secure_compare(token, expected)
      halt 401, { error: "unauthorized" }.to_json
    end

    # Attach a lightweight context for downstream authorization
    @current_hmac_scope = determine_scope_from_token(path, token)
  end

  def secure_compare(a, b)
    return false unless a.bytesize == b.bytesize
    l = a.unpack "C#{a.bytesize}"
    res = 0
    b.each_byte { |byte| res |= byte ^ l.shift }
    res == 0
  end

  def determine_scope_from_token(path, token)
    # Example mapping: derive scope from token prefix or a separate lookup
    # In practice, this might decode a JWT-like structure or query a scoped token table
    { path: path, token: token }
  end
end

In both examples, the Hmac Signature ensures the request has not been altered, but explicit authorization checks (subscription.account_id, policy_scope.report_visible?, or scoped token mapping) enforce that the authenticated party is allowed to act on the specific resource. This combination addresses Broken Access Control by requiring both integrity and per-instance permissions. For ongoing management, middleBrick’s Pro plan supports continuous monitoring and CI/CD integration, so future regressions in authorization logic can be caught before deployment.

Frequently Asked Questions

Does a valid Hmac Signature always mean the request is authorized to access a resource in Hanami?
No. A valid Hmac Signature confirms request integrity and origin, but it does not enforce scope or ownership. You must add per-resource authorization checks (e.g., record ownership, tenant scoping, or policy evaluations) to prevent Broken Access Control / IDOR.
How can I test my Hanami endpoints for Broken Access Control with Hmac Signatures?
Use a black-box scanner like middleBrick to submit requests with valid Hmac Signatures but different resource identifiers. Verify that 403/404 responses are returned when the authenticated context lacks permission. Supplement automated scans with targeted tests that attempt ID enumeration and privilege escalation scenarios.