Api Key Exposure in Fastapi with Redis
Api Key Exposure in Fastapi with Redis — how this specific combination creates or exposes the vulnerability
When Fastapi applications store or cache API keys in Redis, the combination can inadvertently expose secrets through misconfiguration or insecure access patterns. Fastapi often uses Redis as a shared cache for rate-limiting tokens, session data, or temporarily storing key material for validation. If keys are written to Redis without encryption at rest and the Redis instance permits unauthenticated or weakly authenticated connections, any network path that reaches Redis may read or dump keys.
Consider a Fastapi service that caches third-party API credentials in Redis to avoid repeated database lookups:
import redis.asyncio as redis
from fastapi import Fastapi, Depends, HTTPException
app = Fastapi()
r = redis.Redis(host='redis', port=6379, db=0) # Example connection; no password by default
async def get_api_key(key_id: str) -> str:
val = await r.get(f'api_key:{key_id}')
if val is None:
raise HTTPException(status_code=404, detail='Key not found')
return val.decode() if isinstance(val, bytes) else val
This pattern becomes risky if the Redis server is exposed on a public network or if the container/cluster configuration allows open ports. Attackers scanning for exposed Redis instances can connect without a password and use commands like KEYS * or DUMP to extract stored API keys. Even if Redis is bound to localhost, container networking missteps can inadvertently publish the port or allow lateral movement from a compromised service to Redis.
Another exposure vector involves logging and error handling. If Fastapi logs incoming requests that include API keys (e.g., via headers or query parameters) and those logs are aggregated alongside Redis connection details, an attacker who gains access to logs can correlate Redis host/port information with extracted key values. Similarly, if the application serializes Redis responses into error messages or debug output, keys may be exposed in crash reports or monitoring dashboards.
The risk is compounded when the application uses Redis data structures that inadvertently mirror sensitive access patterns. For example, using Redis hashes to group keys by service or environment can reveal the scope and ownership of each key. Without strict network segmentation, TLS, and access controls, the convenience of Redis caching directly increases the blast radius of a leaked API key.
Redis-Specific Remediation in Fastapi — concrete code fixes
To reduce exposure risk, apply Redis-specific hardening measures in Fastapi services. These include enforcing authentication, limiting network exposure, encrypting sensitive values before caching, and avoiding unsafe deserialization.
1. Enforce Redis authentication and TLS
Always configure Redis with a strong password and enable TLS to protect data in transit. In Fastapi, pass these settings when initializing the Redis client:
import redis.asyncio as redis
# Use environment variables for secrets
r = redis.Redis(
host='redis',
port=6380, # TLS port
password='${REDIS_PASSWORD}', # injected securely
ssl=True,
ssl_cert_reqs='required'
)
Ensure the Redis server enforces requirepass in its configuration and that certificates are validated by the client.
2. Restrict network access
Bind Redis to localhost within containers and use network policies or firewall rules to prevent external connections. In Kubernetes, use a NetworkPolicy that only allows pods in the same namespace to reach Redis. Avoid publishing Redis ports to the public internet.
3. Encrypt keys before caching
Do not store raw API keys in Redis. Encrypt values with a strong key managed by a secrets manager before writing to Redis:
from cryptography.fernet import Fernet
import os
key = Fernet.generate_key() # Store this key securely, e.g., in an environment variable
cipher = Fernet(os.getenv('ENCRYPTION_KEY'))
async def store_api_key(key_id: str, raw_key: str) -> None:
encrypted = cipher.encrypt(raw_key.encode())
await r.setex(f'api_key:{key_id}', 3600, encrypted) # short TTL
async def get_api_key_safe(key_id: str) -> str:
encrypted = await r.get(f'api_key:{key_id}')
if encrypted is None:
raise HTTPException(status_code=404, detail='Key not found')
return cipher.decrypt(encrypted).decode()
This ensures that even if Redis is compromised, the attacker cannot use the cached values without the encryption key.
4. Avoid dangerous Redis operations
Limit the commands your application uses. Avoid KEYS in production; use cursor-based scanning if necessary. Disable commands like FLUSHDB or CONFIG for application accounts. Use Redis ACLs to restrict the application user to only the commands it needs (e.g., GET, SET with specific patterns).
5. Sanitize logs and errors
Ensure that API keys are never included in logs, trace data, or error responses. If you must log key identifiers, use opaque tokens instead of raw values:
import logging
logger = logging.getLogger('api')
# Instead of logging the key, log a hashed reference
import hashlib
key_ref = hashlib.sha256(raw_key.encode()).hexdigest()
logger.info('Using key reference %s', key_ref)