Graphql Introspection in Fastapi
How Graphql Introspection Manifests in Fastapi
Graphql Introspection is a powerful feature that allows clients to query the schema of a GraphQL API, but when exposed in production Fastapi applications, it creates significant security risks. Fastapi developers often enable introspection by default when setting up GraphQL endpoints using libraries like strawberry or ariadne, unaware of the security implications.
In Fastapi, Graphql Introspection typically manifests through the /graphql endpoint where attackers can send introspection queries like:
query {
__schema {
types {
name
fields {
name
type {
name
ofType {
name
}
}
}
}
}
}
This query returns the complete schema including all types, mutations, queries, and even private fields that shouldn't be exposed publicly. Fastapi applications using strawberry-graphql often have this enabled by default:
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
app = FastAPI()
@strawberry.type
class Query:
@strawberry.field
def hello(self) -> str:
return "Hello World"
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, email: str) -> bool:
# Implementation
return True
schema = strawberry.Schema(query=Query, mutation=Mutation)
app.include_router(GraphQLRouter(schema))
The above Fastapi code exposes the entire GraphQL schema to anyone who can access the endpoint. Attackers can use this information to craft targeted queries, discover hidden endpoints, identify vulnerable patterns, and even find business logic flaws. For instance, they might discover a deleteUser mutation that wasn't intended for public use, or identify that certain fields return sensitive data like user.email or user.passwordHash.
Fastapi's async nature makes GraphQL particularly attractive, but this also means introspection attacks can be executed rapidly and in parallel, potentially overwhelming the API. The introspection data reveals field argument types, which helps attackers craft parameter manipulation attacks. For example, if they see a searchUsers(email: String!) field, they can immediately attempt SQL injection or NoSQL injection patterns.
Another manifestation occurs when Fastapi applications use GraphQL subscriptions. Introspection reveals subscription types and their arguments, allowing attackers to subscribe to real-time data streams they shouldn't access, potentially leading to data exfiltration or denial of service through subscription flooding.
Fastapi-Specific Detection
Detecting Graphql Introspection vulnerabilities in Fastapi applications requires both manual testing and automated scanning. The most straightforward detection method is sending a standard introspection query to your GraphQL endpoint and examining the response.
Here's a Fastapi-specific detection script you can run:
import requests
# Fastapi GraphQL endpoint (common pattern)
url = "http://localhost:8000/graphql"
# Standard introspection query
query = '''
query IntrospectionQuery {
__schema {
types {
name
kind
description
fields {
name
description
args {
name
description
type {
name
kind
}
}
type {
name
kind
}
}
}
}
}
'''
response = requests.post(url, json={'query': query})
if response.status_code == 200:
data = response.json()
if '__schema' in data.get('data', {}):
print("✓ Introspection is enabled")
print(f"Schema contains {len(data['data']['__schema']['types'])} types")
# Check for sensitive patterns
sensitive_fields = []
for type_def in data['data']['__schema']['types']:
for field in type_def.get('fields', []):
if 'password' in field['name'].lower() or 'secret' in field['name'].lower():
sensitive_fields.append(f"{type_def['name']}.{field['name']}")
if sensitive_fields:
print("⚠ Found potentially sensitive fields:")
for field in sensitive_fields:
print(f" - {field}")
else:
print("✗ Introspection query failed or endpoint not GraphQL")
else:
print(f"✗ HTTP {response.status_code}")
For automated detection in Fastapi applications, middleBrick provides specialized GraphQL scanning that tests for introspection exposure without requiring credentials. The scanner sends introspection queries to your Fastapi GraphQL endpoints and analyzes the response for schema exposure, sensitive field names, and potential attack vectors.
middleBrick's Fastapi-specific detection includes:
- Introspection query execution and schema analysis
- Detection of Fastapi-specific GraphQL patterns (strawberry, ariadne, graphene)
- Identification of sensitive field names and argument types
- Analysis of subscription capabilities and real-time endpoints
- Cross-referencing with OpenAPI specs if available
You can scan your Fastapi GraphQL API using the middleBrick CLI:
npm install -g middlebrick
middlebrick scan https://your-fastapi-app.com/graphql --category graphql
The scanner tests for introspection in under 15 seconds and provides a security score with specific findings about schema exposure, sensitive fields, and remediation guidance tailored to Fastapi applications.
Fastapi-Specific Remediation
Remediating Graphql Introspection in Fastapi requires a multi-layered approach. The most straightforward method is disabling introspection in production using Fastapi's GraphQL library configuration.
For strawberry-graphql in Fastapi:
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
app = FastAPI()
@strawberry.type
class Query:
@strawberry.field
def hello(self) -> str:
return "Hello World"
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, email: str) -> bool:
return True
schema = strawberry.Schema(query=Query, mutation=Mutation)
# Disable introspection in production
if app.env == "production":
graphql_router = GraphQLRouter(
schema,
introspection=False # Disable introspection queries
)
else:
graphql_router = GraphQLRouter(schema)
app.include_router(graphql_router, prefix="/graphql")
For ariadne in Fastapi:
from fastapi import FastAPI
from ariadne import QueryType, make_executable_schema, graphql
from ariadne.contrib.fasted import GraphQL
app = FastAPI()
type_defs = """
type Query {
hello: String!
}
"""
query = QueryType()
@query.field("hello")
async def resolve_hello(*_):
return "Hello World"
schema = make_executable_schema(type_defs, query)
@app.post("/graphql")
async def graphql_server(state, request: Request):
# Custom middleware to block introspection queries
data = await request.json()
if data.get("query").strip().lower().startswith("query {__schema"):
return JSONResponse(
status_code=403,
content={"errors": [{"message": "Introspection disabled"}]}
)
success, result = await graphql.subscribe(schema, data) if state.get("is_subscription") else graphql(schema, data)
return JSONResponse(result)
A more robust approach uses Fastapi middleware to filter introspection queries:
from fastapi import Request, Response
from fastapi.middleware.base import BaseHTTPMiddleware
class GraphQLIntrospectionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.method == "POST" and request.url.path == "/graphql":
data = await request.json()
query = data.get("query", "").strip()
# Block introspection queries (case-insensitive)
if query.lower().startswith("query {__schema") or query.lower().startswith("query {__type"):
return Response(
content={"errors": [{"message": "Introspection disabled in production"}]},
media_type="application/json",
status_code=403
)
return await call_next(request)
app.add_middleware(GraphQLIntrospectionMiddleware)
For comprehensive protection, combine these approaches with role-based access control:
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from functools import wraps
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def require_graphql_admin(fn):
@wraps(fn)
async def wrapper(*args, **kwargs):
# Check if user has admin role
# This is simplified - implement proper auth
if not hasattr(args[0], 'user') or not args[0].user.get('is_admin'):
raise HTTPException(status_code=403, detail="Admin access required")
return await fn(*args, **kwargs)
return wrapper
# Apply to specific mutations
@strawberry.type
class AdminMutation:
@strawberry.mutation
@require_graphql_admin
def delete_user(self, user_id: int) -> bool:
# Only accessible to authenticated admins
return True
Finally, implement logging and monitoring for GraphQL queries to detect suspicious patterns:
from fastapi import BackgroundTasks
async def log_graphql_query(query: str, user_id: int = None):
# Log to database or external service
pass
@app.post("/graphql")
async def graphql_endpoint(
state,
request: Request,
background_tasks: BackgroundTasks
):
data = await request.json()
query = data.get("query", "")
# Log all queries for audit
background_tasks.add_task(log_graphql_query, query, user_id=1)
# Process query...
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |