Cache Poisoning in Hanami with Api Keys
Cache Poisoning in Hanami with Api Keys — how this specific combination creates or exposes the vulnerability
Cache poisoning occurs when an attacker manipulates cached responses so that malicious or incorrect data is served to other users. In Hanami, using API keys as part of request handling can inadvertently contribute to this risk if cached responses are keyed improperly or cached without considering authentication context.
When API keys are used for authorization but the caching layer does not differentiate responses by key, a response generated for one key may be reused for another. For example, an endpoint that returns user-specific data might be cached based only on path and query parameters, ignoring the Authorization or custom X-API-Key header. An attacker who can observe or influence one user’s request might craft a request that causes a poisoned entry to be cached, and subsequent requests with different keys will receive the tainted response.
In Hanami, if you use a Rack-level cache such as Rack::Cache or a reverse proxy cache without including the API key in the cache key, you risk cross-key contamination. A real-world pattern is an endpoint like /api/v1/reports that accepts X-API-Key and returns data filtered by the associated account. If the cache key excludes the API key, User A’s data could be cached and then served to User B, leaking information or causing privilege issues. This is especially concerning when keys are rotated or reused across environments, as stale poisoned entries persist beyond their intended scope.
Another vector involves query parameters combined with headers. Suppose Hanami routes normalize URLs by stripping or reordering headers before caching. An attacker could send a request with a benign API key and a manipulated query (e.g., injecting a cache-control directive or a parameter that changes response headers). If the cache stores this response and serves it to requests with different API keys, the poisoned response may include sensitive headers or content not intended for those keys.
To map this to known attack patterns, consider this as a variant of the BOLA (Broken Level Authorization) and BFLA (Business Logic Flaws) categories, where trust boundaries are not properly enforced across cache layers. The OWASP API Security Top 10 lists Broken Object Level Authorization and Improper Asset Management; cache poisoning fits into these when cached data crosses authorization boundaries. In a supply-chain or SSRF context, if the API key is used to access internal resources, a poisoned cache could redirect or expose data across tenant boundaries.
Because middleBrick scans unauthenticated attack surfaces and tests input validation and property authorization, it can surface risks where cache differentiation by API key is missing. Findings will highlight inconsistencies between spec-defined authentication requirements and runtime behavior, helping you identify endpoints where keys are not properly considered in caching.
Api Keys-Specific Remediation in Hanami — concrete code fixes
To remediate cache poisoning when using API keys in Hanami, ensure cache keys incorporate the key or a normalized representation of authorization context. Avoid caching sensitive or key-specific responses unless you can guarantee isolation. Below are concrete patterns and code examples.
1. Include API key in cache key
When using a caching layer, derive the cache key from the API key and the request path. In Hanami, you can customize the cache middleware or your service objects to include the key.
# app/services/report_cache.rb
class ReportCache
def initialize(cache_store = Hanami::Cache[:default])
@cache = cache_store
end
def fetch(api_key, date_range)
key = "report:#{api_key}:#{date_range}"
@cache.fetch(key, expires_in: 300) do
generate_report(api_key, date_range)
end
end
private
def generate_report(api_key, date_range)
# business logic using api_key to scope data
end
end
This ensures each API key gets its own cache entry, preventing cross-key contamination.
2. Avoid caching sensitive responses
For endpoints that return user-specific data, disable caching when an API key is present. You can set response headers to prevent intermediate caches from storing the response.
# app/actions/api/reports.rb
module Api
class Reports < Hanami::Action
def call(params)
api_key = env['api.key']
if api_key
# Ensure no sensitive data is cached
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
end
# proceed with logic
end
end
end
3. Normalize and validate API keys before caching
Validate and normalize the API key to avoid cache key injection. Do not directly use raw keys in cache keys without sanitization to prevent header or query manipulation attacks.
# app/actions/api/base.rb
module Api
class Base < Hanami::Action
before do
api_key = request.env['api.key']
# Normalize: trim, enforce format, reject suspicious characters
if api_key && api_key.match?(\A[a-zA-Z0-9\-_]{16}\z)
@normalized_key = api_key.strip
else
halt 401, { error: 'invalid_key' }.to_json
end
end
def cache_key(path)
"v1:#{@normalized_key}:#{path}"
end
end
end
4. Use scope-aware caching for collections
If you must cache list endpoints, scope the cache to the API key and include strong validation of filters and pagination parameters to avoid poisoning through query manipulation.
# app/actions/api/items.rb
module Api
class Items < Hanami::Action
def call(params)
api_key = env['api.key']
scope = "items:user:#{api_key}:page:#{params[:page]}:per:#{params[:per]}"
items = Hanami::Cache.fetch(scope, expires_in: 60) do
repository(:items).for_user(api_key, params)
end
render 'items/index', items: items
end
end
end
5. Rotate keys and purge cache on rotation
When rotating API keys, proactively clear or version cached entries to eliminate poisoned data tied to old keys. This can be done with a background task that deletes keyspaced entries.
# lib/tasks/key_rotation.rb
namespace :cache do
desc "Purge cache entries for a given API key prefix"
task purge_key: :environment do
key_prefix = ENV['KEY_PREFIX']
Hanami::Cache.delete_matched("report:#{key_prefix}:*")
end
end
By consistently including the API key in cache keys, disabling caching for sensitive responses, and validating key format, you reduce the risk of cache poisoning in Hanami applications. These practices align with remediation guidance that middleBrick provides in its findings, helping you address authorization and input validation issues flagged during scans.