Cache Poisoning in Hanami
How Cache Poisoning Manifests in Hanami
Cache poisoning in Hanami typically occurs when dynamic request values—such as Accept-Language, X-Forwarded-Host, or cookie fragments—are embedded into cache keys without normalization or validation. Hanami’s default production adapter uses a Rack-based cache, and if the key derivation includes attacker-controlled inputs, a malicious client can cause the application to serve one user’s response to another, or even inject crafted content into the cache. For example, a route like GET /products/:id that varies cache by a raw params[:currency] value can be abused: an attacker requests /products/123?currency=EUR and then requests /products/123?currency=, leading to a poisoned entry that later serves reflected script to other users if the cache is shared across users.
Hanami-specific code paths where this appears include custom cache stores set via config.cache.store and view fragment caching in templates. In a Hanami controller, calling cache([params[:action], params[:id], params[:filter]]) without sanitizing params[:filter] can introduce injection because the cache key becomes mutable by the client. Similarly, using Hanami::View::Partials with dynamic locals that are included in the cache namespace can produce predictable, mutable keys. Attack patterns include locale-based cache poisoning via the Accept-Language header, host-based poisoning via X-Forwarded-Host, and parameter pollution where repeated or unexpected keys alter the cache key in unintended ways, leading to content replacement or cross-user leakage.
Hanami-Specific Detection
To identify cache poisoning in Hanami, focus on how cache keys are constructed in production and whether they include unvalidated request inputs. Review configuration for config.cache.store and any custom key builders. Look for patterns where params, headers, or cookies directly influence the cache namespace without normalization, hashing, or scope isolation. You can detect risky constructs by scanning your Hanami app with middleBrick, which runs 12 security checks in parallel, including BFLA/Privilege Escalation and Input Validation tests that surface cache key manipulation risks. The scanner analyzes your OpenAPI/Swagger spec (2.0, 3.0, 3.1) with full $ref resolution and cross-references spec definitions with runtime findings to highlight endpoints where unauthenticated or user-influenced cache behavior may exist.
Using the middleBrick CLI, run middlebrick scan <url> to perform a black-box scan against your Hanami endpoint. The report will flag findings such as missing input validation on parameters that influence caching, missing rate limiting that enables cache flooding, and data exposure risks where poisoned cache entries could reveal PII. The LLM/AI Security checks unique to middleBrick also probe for indirect risks, such as whether public endpoints inadvertently expose cache controls that could be abused. With the GitHub Action, you can integrate these checks into CI/CD and fail builds if the risk score drops below your chosen threshold, while the Web Dashboard lets you track how cache-related findings evolve as your codebase changes.
Hanami-Specific Remediation
Remediate cache poisoning in Hanami by ensuring cache keys are deterministic, scoped, and free of attacker-controlled variability. Use Hanami’s built-in utilities to normalize inputs before they enter the cache key. For example, instead of directly interpolating params, derive a stable fragment with permitted keys and explicit defaults:
# Good: deterministic, sanitized cache key fragment
currency = params.fetch(:currency, 'USD')
unless %w[USD EUR GBP].include?(currency)
currency = 'USD'
end
cache_key = [params[:action], params[:id], currency]
cache(cache_key) do
# render partial or data
end
When configuring the cache store, scope caches per-tenant or per-user where appropriate to prevent cross-user pollution. Hanami’s support for custom cache stores lets you wrap entries with a namespaced key that excludes volatile headers:
# config/initializers/cache.rb
Hanami.configure do |config|
config.cache = { store: :redis, namespace: 'hanami_app_v1' }
end
In views, avoid injecting raw request values into partial cache names. Instead, map them to a controlled set:
# app/views/products/show.html.erb
<% filter = SafeFilterService.call(params[:filter])
cache([params[:action], params[:id], filter]) do %>
<%= render 'product', product: @product %>
<% end %>
Combine these practices with input validation libraries permitted by Hanami to reject malformed values early, and ensure that shared caches differentiate by tenant or user ID explicitly. These steps reduce the risk of cache poisoning while preserving performance benefits.