Bola Idor in Hanami with Cockroachdb
Bola Idor in Hanami with Cockroachdb — how this specific combination creates or exposes the vulnerability
BOLA (Broken Level of Authorization) / IDOR occurs when an API exposes internal object identifiers (IDs) and does not enforce that the requesting user is authorized to access the specific resource. In Hanami, this often maps to model instances fetched via public-facing keys (e.g., params[:id]) without verifying ownership or tenant context. When you use Cockroachdb as the backend, the risk is shaped by how IDs are generated, how queries are built, and how data is partitioned.
Hanami encourages explicit, intention-revealing routes and uses value objects for IDs (e.g., AccountID). If these IDs are predictable (e.g., integers or UUIDs derived from non-tenant context), and the authorization check is omitted or shallow, an attacker can iterate through IDs and access other users’ data. Cockroachdb’s distributed SQL nature does not prevent BOLA, but certain patterns amplify exposure:
- Multi-tenant data isolation is implemented at the application level rather than the database level. If queries do not include tenant context (e.g.,
account_id), Cockroachdb will return rows it can see, and Hanami must filter them. - Using Cockroachdb-generated UUIDs (with
gen_random_uuid()or via ORM defaults) can produce opaque IDs; if these IDs are exposed in URLs without verifying the caller’s relationship to the record, enumeration is straightforward. - Complex joins or secondary indexes in Cockroachdb can inadvertently expose related records if queries are not constrained by tenant or ownership predicates in Hanami mappers.
A concrete scenario: a Hanami endpoint GET /api/v1/invoices/:id uses InvoiceRepository.new.find(params[:id]). If the repository does not scope by account_id and the invoice IDs are sequential integers or guessable UUIDs, an attacker can request invoices belonging to other accounts. Cockroachdb will serve the row if it exists; Hanami must enforce that the authenticated actor owns or is permitted to view that invoice. Without this check, the combination of a predictable ID space and a permissive query surface creates BOLA/IDOR.
Cockroachdb-Specific Remediation in Hanami — concrete code fixes
Remediation centers on scoping every data access with tenant or ownership context and avoiding reliance on ID obscurity. Below are concrete Hanami patterns and Cockroachdb-aware code examples.
1. Scope queries by tenant/actor
Ensure every repository query includes the actor’s identifier. If you use an Account aggregate, embed account_id in the domain model and enforce it at the mapper layer.
# app/repositories/invoice_repository.rb
class InvoiceRepository
def initialize(account_repo: AccountRepository.new)
@account_repo = account_repo
end
def for_account(account_id)
invoices = DB[:invoices].where(account_id: account_id).to_a
invoices.map { |attrs| Invoice.new(attrs) }
end
def find_by_id(account_id, invoice_id)
row = DB[:invoices].where(id: invoice_id, account_id: account_id).first
row ? Invoice.new(row) : nil
end
end
In your Hanami use case, call invoice_repo.find_by_id(current_account.id, params[:id]). This ensures Cockroachdb only returns rows where both ID and tenant match.
2. Use opaque, unguessable IDs with ownership binding
Prefer UUIDs generated by the application (not solely by Cockroachdb) and bind them to an account at creation time. This prevents ID enumeration across tenants.
# app/entities/invoice.rb
class Invoice
include Hanami::Entity
attribute :id, Types::Strict::String
attribute :account_id, Types::Strict::String
# other attributes...
end
# app/repositories/invoice_repository.rb
class InvoiceRepository
def create(attributes)
id = SecureRandom.uuid
DB[:invoices].insert(id: id, account_id: attributes[:account_id], amount: attributes[:amount])
find_by_id(attributes[:account_id], id)
end
end
When exposing IDs in URLs, use these UUIDs; the authorization check in find_by_id will still validate account_id.
3. Enforce ownership in the domain service, not just the repository
Repositories can be bypassed if multiple entry points exist. Implement a domain service that validates ownership before performing sensitive operations.
# app/services/invoice_access_service.rb
class InvoiceAccessService
def initialize(invoice_repo: InvoiceRepository.new, auth: CurrentAccount)
@invoice_repo = invoice_repo
@auth = auth
end
def view_invoice(invoice_id)
invoice = invoice_repo.find_by_id(@auth.account.id, invoice_id)
raise Hanami::Action::UnauthorizedError unless invoice
invoice
end
end
Use this service in your action: InvoiceAccessService.new(view_invoice: params[:id]).view_invoice. This guarantees that even if a developer forgets to scope a query, the service layer will enforce tenant alignment.
4. Validate input to avoid Cockroachdb-specific edge cases
Cockroachdb supports complex SQL features; ensure your Hanami queries do not inadvertently expose data through joins or CTEs. Always parameterize inputs and avoid interpolating IDs into raw SQL fragments.
# Safe: parameterized query
DB[:invoices].where(Sequel.lit('id = ?', invoice_id)).limit(1).first
# Avoid: string interpolation that may bypass scoping
# DB["SELECT * FROM invoices WHERE id = #{invoice_id}"].first
Also, review secondary indexes and computed columns in Cockroachdb to ensure they do not expose relationships that should remain hidden across tenants.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |