HIGH api key exposurefastapiredis

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)

Frequently Asked Questions

Can Fastapi safely use Redis to cache API keys if Redis is password protected?
Password protection reduces risk but is not sufficient alone. Use TLS, network isolation, and encrypt key values before caching; avoid storing raw keys and prefer short TTLs to limit exposure.
What should I do if I discover an exposed Redis instance with API keys cached?
Rotate all exposed keys immediately, enable auth + TLS, restrict network access, audit logs for exfiltration, and re-encrypt cached values with a keys derived from a secure secrets manager.