Api Key Exposure in Hanami with Firestore
Api Key Exposure in Hanami with Firestore — how this specific combination creates or exposes the vulnerability
Hanami is a Ruby web framework that encourages explicit, modular architecture. When a Hanami application integrates with Google Cloud Firestore, developers often store service account keys or Firestore credentials in environment variables or configuration files. If these secrets are referenced insecurely—for example, embedded in JavaScript bundles, rendered into HTML templates, or logged in application output—the API key or service account token can be exposed to unauthenticated clients.
Firestore access rules can inadvertently permit public read or write when security rules are misconfigured. A common pattern in Hanami apps is to initialize Firestore with a default credential and use it across request handling. If the initialization logic depends on a secret that is accidentally exposed via an open endpoint or debug route, an attacker who observes the exposed key can use it to interact with Firestore outside the intended access boundaries. This becomes a direct path to data exposure, data exfiltration, or privilege escalation, especially when combined with weak IAM bindings.
An attacker may leverage exposed Firestore credentials to enumerate collections, read sensitive records, or modify data if the rules allow. In a Hanami context, this often surfaces through insufficient input validation on document IDs or paths, where user-controlled parameters are concatenated into Firestore document references without proper sanitization. If an endpoint returns configuration or debugging data that includes the project ID or key identifiers, it can aid further exploitation.
The 12 security checks in middleBrick operate in parallel and would flag such exposure under Data Exposure and Authentication checks. System prompt leakage patterns do not apply here, but the scanner evaluates whether runtime responses reveal credentials or sensitive configuration details. For LLM-related integrations—should a Hanami service use AI components—middleBrick’s LLM/AI Security checks would additionally detect exposed keys in model outputs or agent tool usage patterns.
Using middleBrick’s CLI, you can scan a Hanami service’s public endpoints to detect potential key exposure: middlebrick scan https://api.example.com. The report will highlight findings such as potential credential leakage in responses, overly permissive Firestore rules, or missing input validation tied to document paths. The dashboard allows you to track these findings over time, and the Pro plan’s continuous monitoring can schedule regular scans to catch regressions before deployment.
When CI/CD is in use, the GitHub Action can enforce a security score threshold so that builds fail if a scan detects exposed keys or weak configuration. This integrates security into the deployment pipeline for Hanami applications that rely on Firestore, reducing the window during which credentials remain vulnerable.
Firestore-Specific Remediation in Hanami
Remediation focuses on preventing secret leakage and tightening Firestore rules. Store service account keys outside the application runtime using Google Cloud secret management or environment injection at deployment time, and avoid embedding them in source code or logs. In Hanami, configure Firestore initialization in a dedicated file that reads credentials securely and ensures the service account has minimal required permissions.
Use Firestore security rules to enforce strict access controls. For user-specific data, leverage authentication UID matching rather than public access. Validate and sanitize all inputs that influence document paths or collection names to prevent path traversal or wildcard abuse.
Example: Secure Firestore initialization in Hanami without exposing credentials in responses.
# config/firestore.rb
require "google/cloud/firestore"
module HanamiFirestore
def self.client
@client ||= begin
# Use Application Default Credentials in production;
# locally set GOOGLE_APPLICATION_CREDENTIALS to a restricted service account key.
# Do NOT log or serialize this client in responses.
Google::Cloud::Firestore.new(
project_id: ENV.fetch("FIRESTORE_PROJECT_ID"),
credentials: Google::Auth::DefaultCredentials.make_credential(
scopes: ["https://www.googleapis.com/auth/datastore"]
)
)
end
end
end
Example: Firestore security rules that restrict access to authenticated users and scope to owner subcollections.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow read/write only if request.auth is present and resource.data respects ownership
match /users/{userId}/data/{document=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Strict rule for public metadata that does not expose sensitive keys
match /public/metadata/{docId} {
allow read: if true;
allow write: if false;
}
// Catch-all deny for unhandled paths
match /{document=**} {
allow read, write: if false;
}
}
}
Example: Hanami endpoint that safely references Firestore without exposing project or key details in the response.
# lib/operations/fetch_user_data.rb
class FetchUserData
include Hanami::Operation
expose :items, :error
def call(params)
user_id = params[:user_id]
# Validate user_id format before using it in a document path
if user_id.to_s.match?(/^\w{1,50}$/)
doc_path = "users/#{user_id}/data"
snapshot = HanamiFirestore.client.collection(doc_path).get
@items = snapshot.map(&:data)
@error = nil
else
@items = []
@error = "Invalid user identifier"
end
rescue Google::Cloud::Error => e
@items = []
@error = "Unable to load data"
# Ensure errors do not leak project ID or key details
Hanami.logger.warn("Firestore error: #{e.class}")
end
end
Example: A Hanami controller using the operation above, ensuring no sensitive fields are serialized.
# apps/web/controllers/api/data_controller.rb
class Api::DataController < Hanami::Controller
include Import["operations.fetch_user_data"]
before do
# Enforce authentication via your app’s session strategy
halt 401, { error: "unauthorized" }.to_json unless current_user
end
get "/data" do
result = fetch_user_data.call(user_id: params[:user_id])
if result.error
status 400
{ error: result.error }.to_json
else
{ items: result.items }.to_json
end
end
end
These examples focus on minimizing exposure: credentials are read once at initialization, inputs are validated, and responses exclude project metadata. Continuous monitoring via middleBrick Pro can help detect regressions in rules or accidental exposure in future changes.