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.