HIGH broken access controlhanamiruby

Broken Access Control in Hanami (Ruby)

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

Broken Access Control occurs when authorization checks are missing or inconsistent, allowing unauthorized users to access or modify resources they should not. In Hanami, a Ruby web framework that emphasizes explicit, object-oriented design, this typically arises at the intersection of routing, action execution, and domain-level authorization logic. Because Hanami encourages separating use cases into interactor-like objects and keeps controllers thin, developers may assume that routing constraints or view-level checks are sufficient. This assumption can leave endpoints relying only on route definitions or default visibility, creating an access control gap.

Hanami’s routing is explicit and can expose endpoints if route-level protections are not complemented by action-level authorization. For example, a route like resources :documents maps to controller actions that may assume the current user is present and authorized. If the controller action directly loads a record via DocumentRepository.new.find(params[:id]) and returns it without verifying ownership or role-based permissions, any authenticated user who knows or guesses a valid ID can access or manipulate another user’s data. This is a classic Insecure Direct Object Reference (IDOR) pattern, catalogued in the OWASP API Top 10 as Broken Access Control.

Another common scenario involves privilege escalation when role checks are performed in the UI layer but not enforced in the underlying use case or repository. In Hanami, if a controller invokes an operation like CreateDocument without confirming the caller has the required privilege (for example, an admin flag), a user may elevate their permissions by simply invoking the endpoint with crafted parameters. Because Hanami’s default setup does not automatically enforce RBAC/ABAC at the domain boundary, developers must explicitly embed authorization checks within each use case or service object. Without this, the unauthenticated attack surface includes not only endpoints that lack authentication but also those that lack proper authorization, enabling BOLA/IDOR and BFLA/Privilege Escalation findings in a middleBrick scan.

Data exposure is also a risk when listing or searching endpoints return more records than intended. A Hanami action such as Documents::Index that queries DocumentRepository.new.all without scoping to the current user’s organization or tenant can inadvertently expose sensitive documents. This aligns with the broader category of Excessive Data Exposure in LLM/AI Security checks, where model outputs reveal more than necessary. In a middleBrick scan, such endpoints may trigger findings related to Property Authorization and Data Exposure, especially if the OpenAPI spec describes broader scopes than the runtime enforcement.

Because Hanami does not prescribe a built-in authorization layer, developers must consistently apply checks at the point of data access and operation invocation. Frameworks like Hanami make it easy to create clean abstractions, but these abstractions can inadvertently hide missing authorization when developers rely on convention over explicit enforcement. A thorough security approach combines route awareness, controller-level guards, and domain-level policy checks to ensure each request is validated for both authentication and proper authorization, reducing the likelihood of Broken Access Control findings.

Ruby-Specific Remediation in Hanami — concrete code fixes

Remediation in Hanami focuses on embedding explicit authorization checks within use cases and ensuring data queries are scoped to the current user’s context. Below are concrete, idiomatic Ruby examples that demonstrate how to implement these protections.

1. Scoped repository queries

Ensure that data access is limited to what the user is permitted to see. Instead of loading all records, scope the query by the current user’s ID or organization.

module Documents
  class Repository
    def initialize(relation = DocumentRepository.relation)
      @relation = relation
    end

    def for_user(user_id)
      @relation.where(user_id: user_id).to_a
    end

    def find_by_user(user_id, id)
      @relation.where(user_id: user_id, id: id).one!
    end
  end
end

2. Authorize within the use case

Use case objects should explicitly verify permissions before proceeding. This keeps authorization logic close to the operation and avoids accidental bypasses.

module Documents
  class Update
    include Hanami::Interactor

    def initialize(document_repo: DocumentRepository, policy: DocumentPolicy)
      @document_repo = document_repo
      @policy = policy
    end

    def call(params)
      document = @document_repo.find_by_user(current_user.id, params[:id])
      return Failure.new(error: :not_authorized) unless @policy.update?(current_user, document)

      # proceed with update logic
      Success.new(document: document)
    end

    private

    def current_user
      @current_user ||= Context['current.user']
    end
  end
end

3. Policy-based checks

Define a lightweight policy class to encapsulate authorization rules. This keeps controllers and use cases declarative and testable.

class DocumentPolicy
  def initialize(user, record)
    @user = user
    @record = record
  end

  def update?
    @user.admin? || @record.user_id == @user.id
  end

  def view?
    @user.admin? || @record.user_id == @user.id || @record.shared_with.include?(@user.id)
  end
end

4. Controller-level guard

Even with use case checks, enforce authorization at the controller to provide early failure and clear HTTP semantics.

class DocumentsController < Hanami::Controller
  before_action :authenticate_user!

  def show
    document = DocumentRepository.new.find_by_user(current_user.id, params[:id])
    halt 403, { error: 'Forbidden' }.to_json unless DocumentPolicy.new(current_user, document).view?

    render json: DocumentRepresenter.new(document)
  end

  def update
    result = Documents::Update.new.call(params)
    if result.success?
      render json: DocumentRepresenter.new(result[:document])
    else
      halt 403, { error: 'Forbidden' }.to_json
    end
  end

  private

  def current_user
    @current_user ||= UserRepository.new.find(session[:user_id])
  end
end

5. Enforce tenant or organization scoping

For multi-tenant setups, ensure every query includes tenant_id and that users cannot cross-tenant boundaries.

module Documents
  class Repository
    def for_current_tenant(user)
      @relation.where(tenant_id: user.tenant_id).to_a
    end
  end
end

By combining these patterns, Hanami applications can mitigate Broken Access Control risks. Explicit checks at the repository, use case, and controller layers ensure that Ruby’s expressiveness does not come at the cost of authorization bypasses. A middleBrick scan can then validate whether these controls are correctly reflected in the runtime behavior and the OpenAPI specification.

Frequently Asked Questions

Can a middleBrick scan detect missing authorization checks in Hanami APIs?
Yes. middleBrick runs unauthenticated checks that test for BOLA/IDOR and BFLA/Privilege Escalation by probing endpoints with varied identifiers and inspecting whether responses enforce proper authorization. Findings highlight where data exposure or excessive permissions exist.
How does Hanami’s explicit architecture affect security compared to more opinionated frameworks?
Hanami’s explicit separation of concerns means developers must intentionally add authorization; there is no implicit guard. This can reduce magic but requires consistent policy and scoping patterns. If authorization is omitted at the repository, use case, or controller layer, a middleBrick scan is likely to flag Broken Access Control findings.