Cache Poisoning in Hanami with Firestore
Cache Poisoning in Hanami with Firestore — how this specific combination creates or exposes the vulnerability
Cache poisoning in a Hanami application that uses Google Cloud Firestore as a persistence layer occurs when attacker-influenced data is stored in or retrieved from the cache and subsequently used to influence behavior, leak information, or bypass authorization checks. Hanami’s architecture encourages explicit, domain-driven models and service objects; if these services read from caches keyed by untrusted input and then write Firestore entities into that cache, an attacker can manipulate keys, query parameters, or HTTP headers to cause the application to cache and later serve malicious or incorrect data.
Consider a Hanami service that caches Firestore documents by a user-controlled document ID or a path parameter. If the cache key is derived directly from this input without strict validation, an attacker can cause the application to store a poisoned cache entry under a key that other users or privileged workflows later read. For example, an attacker might supply a document ID like users/attacker__admin and, if Firestore permissions are misconfigured or overly permissive, cause the cached read to return data that should remain private. Because Firestore rules are evaluated at read/write time, a cached response that bypasses proper context (such as missing authentication headers or a misapplied emulator mode) can persist incorrect authorization decisions across requests.
The risk is compounded when Firestore emulator configurations or environment-based cache backends are used without proper isolation. In development or testing environments, developers might run the Firestore emulator locally and inadvertently rely on cached data that was populated against a production-like dataset. An attacker who can influence cache keys or cache-control directives might cause Hanami to serve sensitive Firestore entities from the cache to unauthorized clients. Moreover, if cache entries include Firestore document references or tokens, those references can be leveraged in SSRF or privilege-escalation patterns when the cached data is later deserialized and used to construct further Firestore queries.
Because middleBrick tests unauthenticated attack surfaces and checks integrations like Firestore for data exposure and insecure configurations, it can surface cache-poisoning risks that stem from improper key construction, missing input validation, and weak separation between tenant or user contexts. This is particularly important for Hanami services that combine Firestore’s flexible document model with aggressive caching strategies, as inconsistent rule sets and cache invalidation logic can lead to information leakage or inconsistent authorization enforcement.
Firestore-Specific Remediation in Hanami — concrete code fixes
Remediation focuses on strict input validation, canonical cache-key construction, and scoping Firestore reads to the correct tenant and user context. Avoid using raw user input as cache keys; instead, derive keys from authenticated identifiers and normalized paths. Hanami’s entity objects provide a stable basis for this: use the entity’s type and normalized ID to build cache keys, and ensure the Firestore document ID is validated against an allowlist or a strict pattern before use.
Example: a Hanami service that reads a Firestore document safely and caches the result with a canonical key.
require "google/cloud/firestore"
require "digest"
class FirestoreDocumentFetcher
def initialize(entity_name, entity_id)
@entity_name = entity_name
@entity_id = entity_id
@firestore = Google::Cloud::Firestore.new
@collection = @firestore.collection(entity_name)
end
def call
canonical_id = validate_and_normalize_id(@entity_id)
cache_key = "firestore:#{@entity_name}:#{canonical_id}:v1"
cached = Cache.get(cache_key)
return cached if cached
doc = @collection.document(canonical_id).get
raise NotFoundError, "Document does not exist" unless doc.exists?
data = doc.data.merge("id" => canonical_id)
Cache.write(cache_key, data, expires_in: 300)
data
end
private
def validate_and_normalize_id(input)
# Allow only alphanumeric, hyphens, and underscores; reject path traversal or collection-like strings
unless input&.match?(\A[\w\-]+\z)
raise ArgumentError, "Invalid document ID"
end
# Ensure the ID does not contain path-like segments
raise ArgumentError, "Invalid document ID" if input.include?("/")
input
end
end
Example: a Hanami command that writes Firestore data with tenant scoping and strict ID validation.
class UpdateUserPreferences
def initialize(current_user, params)
@current_user = current_user
@params = params
@firestore = Google::Cloud::Firestore.new
end
def call
tenant_id = @current_user.tenant_id
user_id = @current_user.id
# Canonical, scoped path; never trust client-supplied document paths
doc_ref = @firestore.collection("tenants").document(tenant_id).collection("users").document(user_id)
# Validate incoming keys; only allow expected preference keys
allowed = %w[theme notifications language]
updates = @params.slice(*allowed)
doc_ref.update(updates)
# Invalidate any cached entries for this user by namespacing the cache key
Cache.delete("firestore:users:#{tenant_id}:#{user_id}:profile")
{ status: :ok }
end
end
Additional measures include enforcing Firestore security rules that explicitly scope reads and writes to the requesting user or tenant, avoiding emulator endpoints in production cache configurations, and ensuring cache entries do not contain raw Firestore document references or tokens. middleBrick’s checks for data exposure and property authorization can help detect misconfigurations that would otherwise enable cache poisoning across Firestore-backed Hanami services.