Graphql Introspection in Flask with Api Keys
Graphql Introspection in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
GraphQL introspection allows clients to query the schema for types, queries, and mutations. In Flask, if introspection is enabled and the endpoint is protected only by API keys, an unauthenticated or poorly scoped API key can still expose the full schema. This becomes a design risk when API keys are treated as the sole gatekeeper for a GraphQL endpoint, because introspection does not automatically respect authorization logic that developers may assume is enforced.
Consider a Flask app where a single API key is shared across services or embedded in client-side code. An attacker who obtains the key can send an introspection query to the GraphQL route and learn the entire schema, including potentially sensitive types and fields. This information can be used to craft more targeted attacks, such as BOLA/IDOR probes or injection attempts, against operations that are not individually authenticated. The presence of an API key does not imply that introspection should be allowed for all consumers, yet many Flask GraphQL integrations enable introspection by default.
When combined with unauthenticated attack surface testing, middleBrick flags this as an exposure because the API key mechanism does not restrict introspection at the endpoint level. Even if the key is required, if it is leaked or over-permissioned, an attacker can map the API without needing user-level credentials. This intersects with the BOLA/IDOR and Property Authorization checks, where object-level permissions may be missing, and with Unsafe Consumption checks, where schemas may reveal input structures that are vulnerable to injection.
From a compliance mapping perspective, exposing introspection via an API-key-only protected endpoint can violate OWASP API Top 10 A01:2023 broken object level authorization when introspection reveals object relationships, and it can complicate audits under SOC2 and GDPR by enlarging the attack surface unnecessarily. middleBrick’s LLM/AI Security checks are particularly relevant here because schema exposure can indirectly facilitate prompt injection or data exfiltration attempts against any integrated AI components that rely on schema knowledge.
A realistic example endpoint in Flask might look like this, where introspection is enabled and the API key is checked at the route level but not restricted for introspection operations:
from flask import Flask, request, jsonify
from graphene import ObjectType, String, Schema
app = Flask(__name__)
class Query(ObjectType):
hello = String(name=String(default_value='world'))
def resolve_hello(self, info, name):
return f'Hello {name}'
schema = Schema(query=Query)
@app.route('/graphql', methods=['POST'])
def graphql():
api_key = request.headers.get('x-api-key')
if api_key != 'shared-secret-key':
return jsonify({'error': 'unauthorized'}), 401
data = request.get_json(force=True)
# Introspection query is processed without further authorization checks
result = schema.execute(data.get('query'))
return jsonify(result.data)
if __name__ == '__main__':
app.run()
In this example, the API key guards the route, but introspection is still fully operational for any request that includes the key. An attacker with the key can extract the schema and plan further attacks. Even without the key, if the check is bypassed or the key leaks, introspection remains a vector for information disclosure.
Api Keys-Specific Remediation in Flask — concrete code fixes
To mitigate GraphQL introspection risks when using API keys in Flask, you should either disable introspection for production or enforce additional context-based checks that consider the operation type and the key’s intended scope. The simplest and most secure remediation is to disable introspection entirely unless explicitly needed, such as during development.
Below are concrete code examples that show how to disable introspection in the GraphQL schema and how to implement operation-type checks so that introspection is only allowed for specific trusted API keys or during development modes.
Example 1: Disabling introspection in production by setting introspection to False when creating the schema:
from flask import Flask, request, jsonify
from graphene import ObjectType, String, Schema
app = Flask(__name__)
class Query(ObjectType):
hello = String(name=String(default_value='world'))
def resolve_hello(self, info, name):
return f'Hello {name}'
# Disable introspection in production
introspection_enabled = app.config.get('ENV') != 'production'
schema = Schema(query=Query, introspection=introspection_enabled)
@app.route('/graphql', methods=['POST'])
def graphql():
api_key = request.headers.get('x-api-key')
if api_key != 'shared-secret-key':
return jsonify({'error': 'unauthorized'}), 401
data = request.get_json(force=True)
# If the query is an introspection query and introspection is disabled,
# reject it early with a clear message
if not introspection_enabled and data.get('query', '').strip().startswith('__schema'):
return jsonify({'error': 'introspection disabled'}), 403
result = schema.execute(data.get('query'))
return jsonify(result.data)
if __name__ == '__main__':
app.run()
Example 2: Allowing introspection only for a special trusted API key, useful in staging or for admin tooling:
from flask import Flask, request, jsonify
from graphene import ObjectType, String, Schema
app = Flask(__name__)
class Query(ObjectType):
hello = String(name=String(default_value='world'))
def resolve_hello(self, info, name):
return f'Hello {name}'
# Enable introspection but gate it behind a special key
TRUSTED_INTROSPECTION_KEY = 'admin-only-key'
@app.route('/graphql', methods=['POST'])
def graphql():
api_key = request.headers.get('x-api-key')
if api_key != 'shared-secret-key':
return jsonify({'error': 'unauthorized'}), 401
data = request.get_json(force=True)
is_introspection_query = data.get('query', '').strip().startswith('__schema')
if is_introspection_query and api_key != TRUSTED_INTROSPECTION_KEY:
return jsonify({'error': 'introspection requires admin key'}), 403
result = schema.execute(data.get('query'))
return jsonify(result.data)
if __name__ == '__main.guards '__main__':
app.run()
These examples ensure that API keys do not inadvertently permit full schema discovery. In production, disabling introspection or tightly scoping it reduces the risk of exposing object-level relationships that could aid further attacks. middleBrick’s CLI can be used to verify that introspection is appropriately restricted by running scans against the endpoint and reviewing the findings related to BOLA/IDOR and Property Authorization.
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 |