Use After Free in Fastapi with Api Keys
Use After Free in Fastapi with Api Keys — how this combination creates or exposes the vulnerability
Use After Free occurs when memory that has been freed is later accessed, leading to undefined behavior, data leaks, or potential code execution. In the context of FastAPI and API keys, this typically manifests through improper handling of key material or cached key metadata across asynchronous requests or background tasks.
FastAPI applications often rely on API keys for authentication, storing them in memory as strings or byte buffers. If a request processes an API key, completes its work, and a background task or reused buffer still holds a reference to that key, a race condition may allow a subsequent request to observe stale memory. This can expose another caller’s key material if the buffer was not securely zeroed before reuse. The risk increases when keys are stored in mutable objects that are pooled or cached without explicit cleanup.
Consider a scenario where a custom APIRoute or middleware caches resolved key-to-user mappings in an in-memory dictionary. After a request finishes, the entry might be logically removed but the underlying object remains referenced until garbage collection. Between removal and collection, an attacker able to influence timing or inspect logs might infer the key’s presence via side channels. While FastAPI itself does not manage memory manually, the Python runtime’s garbage collection behavior and the developer’s patterns determine whether freed key objects remain accessible.
An example pattern that can contribute to Use After Free-like behavior involves storing API key metadata in global structures without proper invalidation:
from fastapi import FastAPI, Depends, HTTPException, Header
import asyncio
app = FastAPI()
# Global cache mapping key fragments to user IDs
_key_cache = {}
async def get_api_key(x_api_key: str = Header(...)):
if x_api_key in _key_cache:
return _key_cache[x_api_key]
# Simulate expensive lookup
await asyncio.sleep(0.01)
user_id = resolve_user(x_api_key)
_key_cache[x_api_key] = user_id
return user_id
def resolve_user(key: str) -> str:
# Placeholder: in reality this might query a database
return "user-123"
@app.get('/items')
async def read_items(key_info: str = Depends(get_api_key)):
return {"data": "secure", "user": key_info}
If the cache is later mutated or cleared without ensuring the key string is overwritten in memory, the key bytes may persist in the process heap. Although Python strings are immutable, copies or logs might retain references. In multi-tenant setups, this increases the chance that one tenant’s key is inadvertently exposed to another through shared resources or introspection endpoints.
LLM/AI Security checks included in middleBrick can detect unsafe patterns where key material is handled without strict scoping or where outputs might inadvertently expose sensitive values. These checks complement runtime scans that test unauthenticated attack surfaces, including Authentication and Data Exposure categories, to highlight risky key handling.
To mitigate, design key handling so that sensitive objects are short-lived, avoid global caches for raw keys, and rely on FastAPI’s dependency injection with proper scoping. Combine this with infrastructure-level protections and continuous scanning to detect risky patterns before deployment.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on reducing the window during which key material remains accessible and ensuring that key handling does not introduce cross-request contamination. Below are concrete patterns and code examples for secure API key usage in FastAPI.
1. Use FastAPI dependencies with request scope and avoid global caches for raw keys. Dependencies should resolve and immediately use the key, then release references.
from fastapi import FastAPI, Depends, HTTPException, Header, Security
from fastapi.security import APIKeyHeader
import secrets
app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key")
def verify_api_key(key: str = Security(api_key_header)):
# Perform verification without storing the raw key globally
if not key or not secrets.compare_digest(key, ""):
raise HTTPException(status_code=403, detail="Invalid API Key")
# Return a minimal, non-sensitive token or user info
return {"authenticated": True}
@app.get('/secure')
async def secure_endpoint(claims: dict = Depends(verify_api_key)):
return {"message": "OK", "user": claims}
2. Use constant-time comparison to avoid timing leaks when checking keys. secrets.compare_digest prevents early-exit timing attacks that could leak information about the key.
3. Avoid logging or printing API keys. Ensure structured logging excludes header values and that any debug output is scrubbed. Middleware should filter sensitive headers before logging.
from fastapi.middleware import Middleware
from fastapi.middleware.base import BaseHTTPMiddleware
import logging
class SensitiveHeaderFilter(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# Remove sensitive headers from logs in this scope
filtered = request.copy()
filtered.headers = request.headers # headers are immutable; this is illustrative
# In practice, use a logging processor that redacts X-API-Key
response = await call_next(request)
return response
app.add_middleware(SensitiveHeaderFilter)
4. If caching is necessary, store only non-sensitive tokens or hashes, and explicitly clear references when done. Use short TTLs and avoid caching raw keys.
import hashlib
from collections import OrderedDict
class KeyCache:
def __init__(self, maxsize=128):
self.store = OrderedDict()
self.maxsize = maxsize
def store_hash(self, raw_key: str, user_id: str):
h = hashlib.sha256(raw_key.encode()).hexdigest()
self.store[h] = user_id
self.store.move_to_end(h)
if len(self.store) > self.maxsize:
self.store.popitem(last=False)
def get(self, raw_key: str):
h = hashlib.sha256(raw_key.encode()).hexdigest()
return self.store.get(h)
def clear(self, raw_key: str):
h = hashlib.sha256(raw_key.encode()).hexdigest()
self.store.pop(h, None)
key_cache = KeyCache()
def get_user_from_cache(key: str) -> str | None:
return key_cache.get(key)
5. Enforce transport security and use short-lived keys where possible. Rotate keys regularly and revoke compromised keys immediately. middleBrick’s Authentication and Encryption checks can validate that endpoints require authentication and that data in transit is protected.
By combining scoped dependencies, safe comparisons, careful logging, and disciplined caching, you reduce the risk that API key material remains accessible after it should have been freed. Continuous scanning with middleBrick helps identify deviations from these patterns across your API surface.