Graphql Introspection in Flask with Hmac Signatures
Graphql Introspection in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
GraphQL introspection in a Flask application that uses HMAC signatures for request authentication can expose the API’s full schema even when the endpoint is intended for authenticated use. Introspection queries (query { __schema { queryType { name } } }) are typically allowed on unauthenticated endpoints during development, but when HMAC signatures are used to validate request integrity, misconfiguration can allow introspection without a valid signature or alongside a weak signature scheme.
In this combination, the risk arises when introspection is permitted on the same route that relies on HMAC verification but the check is applied after routing or only to specific operations. An attacker can send an introspection query without including the HMAC header, or with a malformed signature, and still receive the schema if the framework does not reject the request early. Because introspection reveals types, queries, and field relationships, this becomes an information-leak that can guide further attacks such as BOLA or IDOR.
For example, if Flask routes all POST requests to a single GraphQL handler and the HMAC validation is performed inside the handler after parsing the query, an attacker can probe with introspection and learn about mutations that change state. Even if the endpoint requires a signature for write operations, the absence of a pre-route validation layer means introspection is not blocked by the HMAC mechanism. The interaction between Flask’s routing and the GraphQL server’s introspection resolver creates a window where schema exposure occurs despite the presence of HMAC signatures.
To align with secure design, treat introspection as a sensitive operation and ensure that HMAC validation is enforced before any GraphQL processing. This prevents attackers from leveraging introspection to bypass intended access controls and reduces the attack surface exposed by unauthenticated probing.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
Remediation centers on enforcing HMAC validation before GraphQL introspection is allowed, and ensuring that introspection is disabled in production or restricted to authorized clients. Below are concrete Flask patterns that integrate HMAC verification and control introspection behavior.
Example 1: HMAC validation before routing to GraphQL
Use a Flask before_request handler to verify the HMAC signature for all incoming requests, including those targeting the GraphQL endpoint. If verification fails, abort before the GraphQL layer runs.
import hashlib
import hmac
from flask import Flask, request, abort
app = Flask(__name__)
SECRET_KEY = b'your-secret-key' # store securely, e.g., from env
def verify_hmac_signature(data: bytes, received_signature: str) -> bool:
expected = hmac.new(SECRET_KEY, data, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_signature)
@app.before_request
def validate_hmac():
if request.method in ('POST', 'PUT'):
signature = request.headers.get('X-API-Signature')
if signature is None:
abort(401, 'Missing signature')
if not verify_hmac_signature(request.get_data(), signature):
abort(401, 'Invalid signature')
Example 2: Disable introspection in production
Conditionally disable introspection by wrapping the GraphQL view or schema. In production, return an error for introspection queries; in development, allow them for debugging.
from flask import jsonify
from graphql import graphql_sync
from graphql.type import GraphQLSchema
INTROSPECTION_DISABLED = True # set via config in prod
def graphql_view():
data = request.get_json()
query = data.get('query', '')
if INTROSPECTION_DISABLED and 'introspection' in query:
return jsonify({'errors': [{'message': 'Introspection is disabled'}]}), 403
result = graphql_sync(schema, query)
return jsonify(result.to_dict())
Example 3: Combine HMAC with operation-type checks
For stricter control, inspect the operation type and require HMAC only for mutations while still blocking introspection regardless of signature validity.
from graphql import parse
def graphql_handler():
data = request.get_json()
query = data.get('query', '')
document = parse(query)
for definition in document.definitions:
if definition.__class__.__name__ == 'OperationDefinition':
if definition.operation == 'query':
# allow query with valid HMAC
if request.headers.get('X-API-Signature') and verify_hmac_signature(request.get_data(), request.headers.get('X-API-Signature')):
result = graphql_sync(schema, query)
return jsonify(result.to_dict())
else:
abort(401, 'Invalid or missing signature for query')
elif definition.operation == 'mutation':
# require HMAC for mutations
if request.headers.get('X-API-Signature') and verify_hmac_signature(request.get_data(), request.headers.get('X-API-Signature')):
result = graphql_sync(schema, query)
return jsonify(result.to_dict())
else:
abort(401, 'Invalid or missing signature for mutation')
# Block introspection explicitly
if 'Introspection' in query:
abort(403, 'Introspection not allowed')
return jsonify({'errors': [{'message': 'Unsupported operation'}]}), 400
Operational recommendations
- Store the HMAC secret outside the codebase (environment variables or a secrets manager).
- Use
hmac.compare_digestto prevent timing attacks when comparing signatures. - Disable introspection in production configurations; enable only in controlled environments.
- Log rejected requests with missing or invalid signatures for audit purposes, but avoid logging raw payloads that may contain sensitive data.
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 |