Cache Poisoning in Grape with Bearer Tokens
Cache Poisoning in Grape with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Cache poisoning in the context of a Grape API that uses Bearer tokens occurs when responses are cached in a way that mixes user-specific authorization data with shared cache keys. If a cache key does not include the token or a user-specific scope, a cached response for one user might be served to another user, exposing protected data or causing authorization bypass.
Grape is a REST-like API micro-framework for Ruby, often used to build JSON APIs. When endpoints accept Authorization headers containing Bearer tokens and also return data that can be cached, improper cache-key design can lead to security issues. For example, if an endpoint caches a response based only on the request path and query parameters, but the response varies by the user represented by the Bearer token, an attacker who can influence what gets cached for their token might cause a victim’s cached response (containing sensitive data) to be served to the attacker.
Consider a scenario where an endpoint returns user profile information and caches the result without incorporating the token or a user ID derived from it. A poisoned cache entry could result in one user seeing another user’s data. Additionally, if tokens are included in URLs as query parameters for debugging or logging, those URLs might be stored in server logs or caches, leading to token leakage and cache poisoning through shared infrastructure.
Real-world parallels exist in findings from scans that highlight missing property-level authorization and weak input validation. If an API does not validate that the requesting user is authorized to view the cached resource and does not scope caching by identity, the combination of Bearer tokens and shared caching creates a pathway for information disclosure. This aligns with common OWASP API Top 10 risks such as Broken Object Level Authorization (BOLA) and Improper Asset Management, where cached representations are not isolated per user or per token.
To illustrate, a vulnerable Grape route might look like this, showing how a token is read but not used to scope caching:
require 'grape'
class VulnerableAPI < Grape::API
format :json
before do
@current_user = authenticate(request.env['HTTP_AUTHORIZATION'])
end
helpers do
def authenticate(auth_header)
token = auth_header.to_s.sub('Bearer ', '')
# Insecure: token used only for authentication, not cache scoping
User.find_by(token: token)
end
end
get :profile do
# Vulnerable: response may be cached without token/user context
cache_key = "profile"
cached = Cache.get(cache_key)
return cached if cached
result = { user: @current_user.id, email: @current_user.email }
Cache.write(cache_key, result, expires_in: 60)
result
end
end
In this example, the cache key is static, so any user’s request could retrieve the same cached data. An attacker could induce a victim to authenticate and cause the victim’s data to be cached under a shared key, then retrieve it using their own request.
Proper mitigation requires including user identity or token scope in the cache key and validating authorization on each request. MiddleBrick’s scans detect such combinations by checking whether authorization is integrated into caching logic and whether input validation and property-level authorization are enforced. Its checks for BOLA/IDOR and Input Validation help surface endpoints where token usage does not properly constrain cached data.
Bearer Tokens-Specific Remediation in Grape — concrete code fixes
To remediate cache poisoning risks with Bearer tokens in Grape, ensure that cache keys incorporate user identity or token-derived values and that each request validates permissions for the specific resource. Avoid using tokens only for authentication without tying them to cache scope.
Here is a secure version of the previous route, where the cache key includes the user ID and the response is scoped to that user:
require 'grape'
class SecureAPI < Grape::API
format :json
before do
@current_user = authenticate(request.env['HTTP_AUTHORIZATION'])
error!('Unauthorized', 401) unless @current_user
end
helpers do
def authenticate(auth_header)
return nil unless auth_header&.start_with?('Bearer ')
token = auth_header.to_s.sub('Bearer ', '')
User.find_by(token: token)
end
end
get :profile do
# Secure: cache key includes user identifier
cache_key = "profile-#{@current_user.id}"
cached = Cache.get(cache_key)
return cached if cached
result = { user: @current_user.id, email: @current_user.email }
Cache.write(cache_key, result, expires_in: 60)
result
end
end
This approach ensures that each user receives their own cached response, preventing cross-user data exposure. Including the user ID in the key directly ties the cached content to the identity derived from the Bearer token.
Additional remediation steps include validating that the token has the necessary scope for the requested operation and avoiding the use of tokens in URLs. MiddleBrick’s scans check for these patterns; its findings often map to OWASP API Top 10 categories and can be reviewed in the dashboard or via the CLI using middlebrick scan <url>. For teams using CI/CD, the GitHub Action can enforce thresholds so that builds fail if insecure caching patterns are detected.
When using the Pro plan, continuous monitoring will flag new endpoints that introduce similar risks, and the MCP Server allows you to run scans directly from your AI coding assistant, helping catch insecure cache usage before deployment. The remediation guidance provided with each finding includes concrete steps such as scoping cache keys, enforcing property-level authorization, and validating input to prevent injection or tampering that could lead to cache poisoning.