Api Key Exposure in Fastapi with Mongodb
Api Key Exposure in Fastapi with Mongodb — how this specific combination creates or exposes the vulnerability
When an API built with Fastapi uses MongoDB as a backend data store, improper handling of API keys can expose sensitive credentials through multiple vectors. In this stack, developers often store or reference API keys in documents within MongoDB collections, and those documents may be inadvertently returned to clients or logged in ways that reveal the keys.
For example, an endpoint that retrieves a user configuration document from MongoDB might include an api_key field. If the response serialization does not explicitly exclude this field, the key is sent to the client. A common pattern that leads to exposure looks like this:
from fastapi import FastAPI, HTTPException
from pymongo import MongoClient
from bson import json_util
import json
app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]
@app.get("/config")
async def get_config(user_id: str):
collection = db["user_configs"]
doc = collection.find_one({"user_id": user_id})
if doc is None:
raise HTTPException(status_code=404, detail="not found")
# Risk: returning the raw document may include api_key
return json.loads(json_util.dumps(doc))
If the user_configs collection stores documents with an api_key field, this endpoint leaks it. Attackers who obtain a valid user ID can harvest API keys and abuse associated downstream services.
Another vector specific to the Fastapi + MongoDB combination arises from logging and error handling. If a MongoDB driver error (e.g., a network timeout or duplicate key error) includes a document or query in its message, and that message is returned in an HTTP 500 response, an API key can be reflected in logs or error responses. Additionally, misconfigured CORS or overly broad route permissions can allow unauthorized origins to invoke endpoints that return sensitive documents.
Middleware or instrumentation that captures request and response bodies for observability may also store or display API keys if fields are not masked. Because Fastapi automatically generates OpenAPI documentation from type hints, developers might inadvertently document endpoints that return keys if response models include those fields without considering exposure.
Cross-referencing the OpenAPI spec with runtime behavior is valuable: if the spec defines a response schema containing an api_key property but the endpoint should never return it, this mismatch indicates a design flaw. middleBrick can detect such inconsistencies by comparing the declared spec against actual runtime responses, highlighting unintended data exposure across the API surface.
Mongodb-Specific Remediation in Fastapi — concrete code fixes
To prevent API key exposure in Fastapi with MongoDB, explicitly control which fields are serialized and returned. Use projection to exclude sensitive fields at the database query level, and define response models that omit keys entirely.
1) Use projection to exclude the api_key field when querying:
from fastapi import FastAPI, HTTPException
from pymongo import MongoClient
app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]
@app.get("/config")
async def get_config(user_id: str):
collection = db["user_configs"]
# Exclude the api_key field from the returned document
doc = collection.find_one({"user_id": user_id}, {"api_key": 0, "_id": 0})
if doc is None:
raise HTTPException(status_code=404, detail="not found")
return doc
Setting {"api_key": 0} ensures the key is omitted from the query result. Also exclude _id if it is not needed, or explicitly include only required fields with {field: 1} style projection.
2) Define a response model that does not include the key and use it to serialize output:
from fastapi import FastAPI, HTTPException
from pymongo import MongoClient
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]
class ConfigResponse(BaseModel):
setting_name: str
setting_value: str
# Do not include api_key here
@app.get("/config", response_model=ConfigResponse)
async def get_config(user_id: str):
collection = db["user_configs"]
# Fetch with projection to avoid pulling the key at all
doc = collection.find_one(
{"user_id": user_id},
{"api_key": 0, "setting_name": 1, "setting_value": 1, "_id": 0}
)
if doc is None:
raise HTTPException(status_code=404, detail="not found")
# Safe: doc matches ConfigResponse shape and contains no api_key
return ConfigResponse(**doc)
3) Harden error handling to avoid leaking documents in stack traces or messages. Do not include the full query or document in HTTP 500 responses:
import logging
logger = logging.getLogger("api")
@app.get("/config")
async def get_config(user_id: str):
try:
collection = db["user_configs"]
doc = collection.find_one({"user_id": user_id}, {"api_key": 0, "_id": 0})
if doc is None:
raise HTTPException(status_code=404, detail="not found")
return doc
except Exception as ex:
# Log safely without exposing request details that might contain keys
logger.warning("Config retrieval failed for user_id: %s", user_id, exc_info=True)
raise HTTPException(status_code=500, detail="internal server error")
4) Review MongoDB connection strings and session usage to ensure keys are not stored in logs or in database metadata. Use environment variables or a secrets manager to provide connection credentials, and avoid embedding keys in application code or documents where they can be read back unintentionally.
5) Validate and sanitize inputs to reduce injection or overly broad query risks, and enforce principle of least privilege for the MongoDB user your application uses. Combine these practices with regular scanning strategies that compare your OpenAPI specification against runtime behavior to catch mismatches before they reach production.