Exposed FastAPI Documentation Page
FastAPI automatically generates interactive API documentation using OpenAPI (Swagger UI) and ReDoc. By default, these documentation pages are exposed at /docs and /redoc endpoints without any authentication.
While this feature is extremely useful during development, leaving it enabled in production environments can lead to full API documentation leakage, exposing sensitive information about the application’s internal structure, endpoints, parameters, and data models.
This information disclosure significantly reduces the reconnaissance effort required for an attacker and provides a detailed roadmap for further exploitation.
Application Example
The following FastAPI application demonstrates a common scenario where sensitive administrative endpoints are exposed through the default documentation:
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
from typing import Optional
app = FastAPI(
title="Internal User Management API",
description="API for managing user accounts and administrative operations",
version="1.0.0"
)
# Simulated user database
users_db = {
"admin": {"username": "admin", "role": "administrator", "api_key": "sk-admin-secret-key-12345"},
"user1": {"username": "user1", "role": "user", "api_key": "sk-user-key-67890"}
}
class UserResponse(BaseModel):
username: str
role: str
class AdminConfig(BaseModel):
debug_mode: bool
database_url: str
secret_key: str
@app.get("/api/v1/users/{username}", response_model=UserResponse)
def get_user(username: str, x_api_key: Optional[str] = Header(None)):
"""
Retrieve user information by username.
Requires valid API key in X-API-Key header.
"""
if username not in users_db:
raise HTTPException(status_code=404, detail="User not found")
user = users_db[username]
return UserResponse(username=user["username"], role=user["role"])
@app.get("/api/v1/admin/config", response_model=AdminConfig)
def get_admin_config(x_admin_token: str = Header(...)):
"""
Retrieve sensitive application configuration.
Restricted to administrators with valid admin token.
Internal endpoint - do not expose publicly.
"""
return AdminConfig(
debug_mode=True,
database_url="postgresql://admin:P@ssw0rd123@internal-db.local:5432/production",
secret_key="jwt-signing-key-super-secret-value"
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Exploitation
This issue comes from how FastAPI initializes its documentation endpoints by default. When you create a FastAPI application:
app = FastAPI()
FastAPI automatically registers three documentation-related routes:
/docs- Swagger UI interactive documentation/redoc- ReDoc alternative documentation interface/openapi.json- Raw OpenAPI specification in JSON format
These endpoints are enabled without any authentication and are immediately accessible to anyone who can reach the application.
Looking at our vulnerable code, the initialization includes metadata that further enriches the exposed documentation:
app = FastAPI(
title="Internal User Management API",
description="API for managing user accounts and administrative operations",
version="1.0.0"
)
This metadata appears directly in the /docs page, revealing the application’s purpose and internal naming conventions to any visitor.
When an attacker discovers the application running on port 8000, accessing the default documentation path:
http://target:8000/docs
The Swagger UI interface renders a complete interactive view of the API, exposing:
- The
/api/v1/users/{username}endpoint with its authentication header requirement - The
/api/v1/admin/configendpoint that returns sensitive configuration data - The exact header names required for authentication (
X-API-Key,X-Admin-Token) - Response schemas showing what data fields are returned
The /openapi.json endpoint provides the same information in a machine-readable format:
curl http://target:8000/openapi.json | jq
This JSON output can be parsed programmatically for automated reconnaissance and attack scripting.
Why exposed FastAPI docs matter from an offensive security perspective
Exposed docs are the first thing I look for when I find a FastAPI target, because they collapse the reconnaissance phase entirely. Instead of fuzzing routes and guessing parameter names, I pull /openapi.json and get the complete, machine-readable map of every endpoint, every required header, every request body schema, and every response field. That turns blind black-box testing into a precise, scripted attack plan. The internal endpoints that developers assume are obscure are the ones the schema advertises most clearly, like the /api/v1/admin/config route here that the comments explicitly mark as internal.
The schema does more than list paths. It tells me where the soft spots are: which routes take a header but never validate it, which ones return secrets, which version of the API I am hitting, and what the data models look like before I send a single malicious request. I look for these tells:
/docs,/redoc, or/openapi.jsonreturning 200 on a production host or behind a production hostname.- Endpoint titles and descriptions that name internal systems, like “Internal User Management API”, which confirm I am on something that was never meant to be public.
- Auth declared as a bare
Header(...)with no dependency that checks it, which signals a header-present-but-unvalidated control I can satisfy with any value. - Admin, debug, config, or internal paths in the
pathslist that the schema hands me for free.
The defender takeaway: treat the OpenAPI schema as a published attack plan, and disable or authenticate docs_url, redoc_url, and openapi_url in production.
Proof of exploitation
Run the lab app (PyFuLabs/fastapi-fu/fastapi-docs-exposure). The interactive docs are live and the OpenAPI schema enumerates an endpoint the comments call internal:
curl -s -o /dev/null -w "/docs -> HTTP %{http_code}\n" "http://pyfu.local/fastapi-fu/fastapi-docs-exposure/docs"
curl -s "http://pyfu.local/fastapi-fu/fastapi-docs-exposure/openapi.json" | jq -r '.paths | keys[]'
/docs -> HTTP 200
/api/v1/users/{username}
/api/v1/admin/config
That admin endpoint’s only control is requiring a header, which is never validated, so any value leaks the secrets:
curl -s "http://pyfu.local/fastapi-fu/fastapi-docs-exposure/api/v1/admin/config" -H "x-admin-token: anything"
{"debug_mode":true,"database_url":"postgresql://admin:P@ssw0rd123@internal-db.local:5432/production","secret_key":"jwt-signing-key-super-secret-value"}
Mitigation
To prevent documentation exposure in production, disable the automatic docs generation:
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
Alternatively, protect the documentation endpoints with authentication or restrict access to internal networks only.