Cache Poisoning in Hanami with Cockroachdb
Cache Poisoning in Hanami with Cockroachdb — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker causes a cache to store malicious or incorrect data, which is then served to other users. In a Hanami application using Cockroachdb as the backend datastore, this risk arises when cache keys are derived from attacker-controlled input without proper validation or normalization. Hanami’s default caching layer does not automatically isolate cache entries by tenant or by authenticated context; if a cache key includes an identifier that an attacker can influence, they can inject data under a shared key and poison the cache for other users.
With Cockroachdb, the distributed SQL nature means that queries are routed to nodes that may serve stale or previously cached results if application-level caching is layered on top of database reads. For example, consider a Hanami endpoint that caches user profile data using a key like profile-#{params[:user_id]}. If params[:user_id] is not strictly validated and can be set to another user’s numeric ID, an attacker can request their own profile under a different ID and overwrite the shared cache entry. Subsequent requests for that ID will receive the attacker’s data, leading to information disclosure or privilege confusion. This is a BOLA/IDOR pattern that maps to OWASP API Top 10 API1:2023 broken object level authorization.
The interaction with Cockroachdb can exacerbate visibility issues because Cockroachdb’s transaction isolation and distributed reads may not immediately reflect invalidated cache entries if the application does not implement cache invalidation logic. In a Hanami app, if a write updates a record in Cockroachdb but the cache is not cleared, readers may continue to receive poisoned data until TTL expires. Moreover, if the cache key includes query parameters or headers that are not canonicalized, an attacker can vary casing, whitespace, or encoding to bypass naive cache-key deduplication, effectively creating multiple poisoned entries that persist across requests.
Real-world exploitation steps include: (1) identify an endpoint that caches responses keyed by user input; (2) determine whether cache keys incorporate authentication context; (3) craft requests that place attacker-controlled data into the cache under a key used by other users; (4) verify that poisoned data is returned to other users or across sessions. Because middleBrick scans test unauthenticated attack surfaces and include checks such as BOLA/IDOR and Input Validation, it can surface these risks by correlating endpoint behavior with unsafe caching patterns and data exposure findings.
Cockroachdb-Specific Remediation in Hanami — concrete code fixes
Remediation focuses on ensuring cache keys are deterministic, scoped to the correct tenant and user context, and that validation precedes cache lookup or write. Below are concrete Hanami + Cockroachdb examples that demonstrate safe patterns.
1. Canonicalize and scope cache keys
Always include the current user ID and a tenant ID in the cache key, and normalize input to prevent key duplication via encoding tricks.
# app/services/profile_cache.rb
module ProfileCache
def self.key(user_id, requested_id)
# Ensure requested_id is integer and matches the authenticated user unless admin
normalized_id = requested_id.to_i.abs.to_s
"profile:v2:#{user.id}:#{normalized_id}"
end
end
# app/actions/api/profiles/show.rb
module API
module Profiles
class Show < Hanami::Action
def handle(request)
user_id = request.session[:user_id]
requested_id = request.params.fetch(:id, user_id)
cache_key = ProfileCache.key(user_id, requested_id)
cached = Rails.cache.read(cache_key)
if cached
response.body = cached
return
end
# Cockroachdb query using Sequel with placeholders
profile = DB[:profiles]
.where(id: requested_id.to_i, user_id: user_id)
.limit(1)
.one
if profile
Rails.cache.write(cache_key, profile, expires_in: 300)
response.body = profile.to_json
else
response.status = 404
response.body = { error: 'not_found' }.to_json
end
end
end
end
end
2. Use Cockroachdb transactions with explicit cache invalidation
When data changes, invalidate or update the relevant cache entries within the same transactional boundary to avoid stale poisoned data.
# app/actions/api/profiles/update.rb
module API
module Profiles
class Update < Hanami::Action
def handle(request)
user_id = request.session[:user_id]
profile_id = request.params.fetch(:id).to_i
DB.transaction do
# Cockroachdb write with parameterized query
DB[:profiles]
.where(id: profile_id, user_id: user_id)
.update(name: request.params[:name], email: request.params[:email])
# Invalidate the cache key for this user+profile combination
cache_key = ProfileCache.key(user_id, profile_id)
Rails.cache.delete(cache_key)
end
response.status = 200
response.body = { status: 'updated' }.to_json
end
end
end
end
3. Enforce input validation and allowlist IDs
Reject non-integer IDs and IDs that do not belong to the requester unless elevated privileges are verified. This prevents cache key pollution via unexpected values.
# app/validators/profile_params.rb
module Validators
class ProfileParams
def self.safe_id!(input, current_user_id)
id = Integer(input) rescue nil
raise Hanami::Action::InvalidParameter, 'invalid_id' unless id
raise Hanami::Action::Forbidden, 'cross_user_access' unless id == current_user_id || User.find(current_user_id).admin?
id
end
end
end
4. Configure cache store with secure defaults
Ensure the cache store does not share keys across users and respects namespace isolation. For Rails cache with Cockroachdb-backed store, use namespace and key constraints.
# config/initializers/cache.rb
Rails.cache = ActiveSupport::Cache::RedisCacheStore.new(
url: ENV['REDIS_URL'],
namespace: 'hanami:cache:v1'
)