HIGH graphql introspectionflaskhmac signatures

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_digest to 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 IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Can HMAC signatures alone prevent GraphQL introspection leaks?
HMAC signatures help ensure request integrity, but they do not inherently block introspection. You must explicitly disable introspection or add pre-route checks; otherwise, an attacker may send introspection queries without a valid signature and learn the schema.
What is a safe approach to allow introspection for trusted clients in Flask?
Restrict introspection by IP or API key and enforce HMAC validation for all requests. For example, allow introspection only from a known internal IP and require a valid HMAC signature; reject introspection for external or unverified sources.