Broken Access Control in Hanami with Firestore
Broken Access Control in Hanami with Firestore — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when Hanami applications fail to enforce proper authorization checks between users and Firestore resources. Because Hanami is a Ruby web framework with an object-oriented architecture, developers often map Firestore documents directly to domain models and assume visibility in one layer implies visibility across the stack. This assumption is dangerous when Firestore security rules are not aligned with Hanami’s authorization logic.
In a typical Hanami + Firestore setup, the client (web or mobile) may call an endpoint such as /api/v1/notes/:id. Hanami loads the requested record from Firestore using a document ID that may be user-provided. If the application verifies only that a user is authenticated and does not confirm that the requested document belongs to the user’s tenant or scope, an authenticated user can change the document ID to access another user’s data. Because Firestore rules may be misconfigured to allow broad read access in development or may rely only on simplistic uid-based rules, the vulnerability becomes exploitable in production when rules are more permissive than intended.
Firestore-specific weaknesses that amplify Broken Access Control include:
- Rule scope gaps: Rules that allow
readorwritebased only on authentication (request.auth != null) without validating tenant IDs or ownership fields. - Overly permissive wildcard rules: Rules using
allow read, write: if true;or broad path patterns during prototyping that are never tightened before deployment. - Insecure document ID handling: Using predictable or sequential IDs that make enumeration trivial, enabling attackers to iterate through valid document references.
Because middleBrick scans the unauthenticated attack surface, it can detect endpoints where Firestore rules permit broader access than Hanami’s business logic enforces. Findings typically highlight mismatches between Firestore rule conditions and Hanami’s authorization checks, such as missing ownership validation or missing resource-level constraints. Attack patterns like BOLA (Broken Object Level Authorization) often map to this issue, where an attacker manipulates identifiers to gain unauthorized access across user boundaries.
Firestore-Specific Remediation in Hanami — concrete code fixes
To remediate Broken Access Control, align Firestore security rules with Hanami’s authorization model and enforce ownership checks on every request. Below are concrete, realistic examples for Hanami apps using Firestore via the official Google Cloud client library.
1. Firestore security rules (Firestore in Native mode)
Define rules that scope reads and writes to the authenticated user’s data. Use request.auth.uid and a structured path that includes a tenant or user segment.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to read/write only their own notes under their user document
match /users/{userId}/notes/{noteId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Example: scoped collection for team resources with explicit role checks
match /teams/{teamId}/members/{memberId} {
allow read: if request.auth != null && isTeamMember(request.auth.uid, teamId);
allow write: if request.auth != null && request.auth.uid == memberId;
}
// Helper to check membership (simplified)
function isTeamMember(uid, teamId) {
return exists(/databases/$(database)/documents/teams/$(teamId)/members/$(uid));
}
}
}
2. Hanami endpoint with explicit ownership check
In your Hanami endpoint, resolve the current user’s ID from the session or token, then use it to scope the Firestore query. Avoid using raw IDs from params directly.
# app/actions/api/notes/show.rb
module Api::Notes
class Show
include Hanami::Action
def initialize(notes_repo: Repositories::Notes.new, auth: Auth.current_user_method)
@notes_repo = notes_repo
@auth = auth
end
def call(params)
user = @auth.call(params)
return halt(401, { error: 'unauthorized' }.to_json) unless user
note = @notes_repo.find_by_id_and_user(params[:id], user.id)
if note
Response::JSON.new(note, status: 200)
else
halt(404, { error: 'not_found' }.to_json)
end
end
end
# app/repositories/notes.rb
module Repositories
class Notes
def initialize(gateway: Gateways::Firestore.new)
@gateway = gateway
end
def find_by_id_and_user(id, user_id)
doc = @gateway.collection('users').doc(user_id).collection('notes').doc(id).get
doc.exists? ? doc.data.merge('id' => doc.id) : nil
end
end
module Gateways
class Firestore
def initialize(project_id: ENV['FIRESTORE_PROJECT_ID'])
@client = Google::Cloud.firestore project: project_id
end
def collection(path)
@client.collection(path)
end
end
end
end
3. Defense-in-depth practices
- Validate and normalize IDs in Hanami to avoid enumeration; prefer UUIDs over sequential integers.
- Use Firestore
getwith explicit path composition that includes the user ID, rather than querying by arbitrary client-supplied references. - Log access denials and monitor rule evaluation outcomes in Cloud Logging to detect attempted bypasses.
By combining precise Firestore rules that enforce ownership at the database level with Hanami’s own authorization checks, you reduce the attack surface for Broken Access Control. middleBrick can verify that your published rules and runtime behavior are consistent, surfacing misconfigurations before they are exploited.