Blog
AppSec Blog

GraphQL security testing checklist

 - 
August 3, 2026

From test schema exposure, resolver authorization, BOLA, injection, query abuse, CSRF, and subscriptions with short requests to expected outcomes and practical remediation guidance.

This practical GraphQL security testing checklist covers schema exposure, authentication, resolver and field authorization, broken object-level authorization (BOLA), injection, query abuse, cross-site request forgery (CSRF), subscriptions, and production hardening. It follows the path a tester would take – discovering the service, mapping the schema, establishing identity, testing access control, probing resolver inputs, assessing resource limits, and verifying production controls – and provides short representative requests, expected outcomes, and remediation guidance throughout.

Use these tests only on systems you own or have explicit authorization to assess, and run any resource-consumption checks in a controlled environment with monitoring and strict limits.

You information will be kept Private
Table of Contents
GraphQL security testing is the systematic assessment of a GraphQL API's schema exposure, authentication, resolver and field authorization, object access, input handling, query cost, batching, browser security, and subscription behavior.

Key takeaways

  • Unlike REST API testing, GraphQL security testing needs to cover schema exposure, resolver behavior, object and field authorization, subscriptions, and demand controls rather than routes alone.
  • Resolver-level authorization, BOLA, and broken function-level authorization are the main access-control priorities.
  • Batching, aliases, nested relationships, and client-controlled query structure require limits based on operation count, identity, and execution cost.
  • Manual testing provides the business context needed for authorization and workflow checks, while automated API DAST can automate repeatable GraphQL runtime tests such as introspection, injection, batching, CSRF, query abuse, and error leakage.

GraphQL security testing at a glance

How to use this checklist

The steps below can be used in a few different ways for different purposes:

  • For penetration testing, work through the sections in sequence.
  • For development work, treat the secure outcomes as implementation requirements.
  • For continuous testing, automate repeatable runtime checks and retain manual multi-user testing for authorization and business logic.

How to rate GraphQL findings

The practical severity of findings should depend on the outcome and context, not the test technique alone:

  • Critical – account takeover, privilege escalation, destructive unauthenticated mutation, or unrestricted access to highly sensitive cross-tenant data
  • High – exploitable injection, unauthorized object modification, practical authentication bypass, or reproducible production-impacting denial of service
  • Medium – schema disclosure, verbose errors, exposed development tooling, or missing demand controls without demonstrated service impact
  • Low or informational – configuration observations without sensitive exposure or a practical attack path

Why GraphQL needs its own security checklist

A REST API usually exposes multiple routes tied to specific resources or operations – but a GraphQL API often concentrates many queries, mutations, and subscriptions behind a small number of endpoints.

GraphQL coverage therefore depends on far more than URLs. Testers need to account for:

  • Schema types and fields
  • Query, mutation, and subscription operations
  • Resolver behavior
  • Nested object relationships
  • User and tenant context
  • Query cost
  • Alternate transports
  • Client-controlled selection sets

While GraphQL's type system validates whether a request matches the schema, it does not automatically provide authentication, authorization, semantic input validation, safe database access, rate limiting, query limits, or secure error handling.

Prerequisites before you start GraphQL testing

To get the best results and be able to use all the techniques listed here, set up at least:

  • One unauthenticated session
  • Two same-role users
  • Two users from different tenants, where relevant
  • One elevated user
  • One administrator, where permitted

Having two same-role users is essential for reliable BOLA testing.

Additional tips and tricks for setting up:

  • If you plan on doing tenant-isolation testing, provision accounts in separate tenants before the assessment. Two users in the same tenant are not sufficient to validate cross-tenant authorization.
  • Capture legitimate queries, mutations, and subscriptions before modifying them. Preserve the original request and change one element at a time.
  • For depth, batching, alias, and complexity tests, begin with small requests and increase them gradually while monitoring application and database behavior.

GraphQL security testing checklist

The following tests are organized in the order a practitioner would typically follow during an assessment – from endpoint discovery and schema analysis through authentication, authorization, injection, denial-of-service controls, browser security, subscriptions, and production hardening. Each section specifies a representative request, what to look for, the expected secure outcome, and where automation can help.

1. Discover GraphQL endpoints and implementations

Common paths include:

/graphql
/api/graphql
/graphql/api
/graphql/v1
/query
/gql

Also search JavaScript bundles, mobile traffic, documentation, gateway configuration, and proxy history for fields such as query, mutation, variables, and operationName.

Representative test:

query {
  __typename
}

A GraphQL-aware response might be:

{
  "data": {
    "__typename": "Query"
  }
}

A syntax or validation error can also confirm that the endpoint parses GraphQL.

Check for alternate endpoints, unexpected methods and content types, and exposed development interfaces such as GraphiQL, Playground, Apollo Explorer, Altair, or Voyager.

Typical severity: Low to Medium, but higher when an alternate endpoint bypasses controls.

Useful manual tools: InQL, Burp Suite, OWASP ZAP, GraphiQL, Altair, and graphql-cop.

Automated testing: Invicti can detect GraphQL endpoints, identify over a dozen supported GraphQL libraries, and detect exposed GraphiQL Explorer and Playground interfaces.

2. Test GraphQL introspection security, schema metadata, and field suggestions

Introspection is a legitimate GraphQL capability, not automatically a vulnerability. The question is whether it should be available to a specific user in a specific environment.

Representative introspection test:

query {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
  }
}

To test a specific type:

query {
  __type(name: "User") {
    fields {
      name
      description
      isDeprecated
      deprecationReason
    }
  }
}

Review not only names but also descriptions, deprecated fields, custom directives, administrative operations, and sensitive arguments.

Also test field suggestions using a misspelled field:

query {
  usr {
    id
  }
}

A revealing response might suggest user or users, helping reconstruct the schema even when introspection is disabled.

Vulnerable result: Unauthenticated users can retrieve sensitive schema details, administrative operation names, or useful implementation metadata.

Expected result: Exposure follows a deliberate production policy, metadata contains no sensitive details, and protected fields remain inaccessible even when their names are known.

Typical severity: Medium for disclosure alone.

Useful manual tools: GraphiQL, Altair, GraphQL Voyager, InQL, Clairvoyance, and graphql-cop.

Automated testing: Invicti detects introspection, field suggestions, and auto-correct behavior.

3. Test authentication and token handling

Authentication only establishes who the requester is, but it does not establish what that identity may access (authorization).

Representative query:

query {
  currentUser {
    id
    email
  }
}

Repeat the query after removing the credential, replacing it with an invalid or expired token, using another user's token, moving the token between cookie and header, or changing the transport.

Test a harmless mutation, for example:

mutation {
  updateProfile(input: { displayName: "Test" }) {
    id
  }
}

Where JSON Web Tokens (JWTs) are used, verify JWT security:

  • Signature validation
  • Rejection of unsigned tokens such as alg: none
  • Protection against algorithm confusion
  • Resistance to offline guessing of weak HMAC secrets
  • Issuer and audience validation
  • Expiry and not-before checks
  • Role and tenant claims
  • Cross-service token reuse

Note that weak HMAC-secret testing is relevant only where symmetric JWT signing is used. Do not treat a decoded or modified token as an exploit unless the server accepts it.

Vulnerable result: Protected queries or mutations execute with missing, invalid, expired, unsigned, or incorrectly scoped credentials.

Expected result: Protected operations require valid authentication consistently across endpoints and transports. Long-lived connections do not preserve invalid access indefinitely.

Typical severity: High; Critical when the issue provides broad account or administrative access.

Automated testing: Invicti detects unauthenticated GraphQL mutations.

4. Test GraphQL authorization at resolver and field level

GraphQL authorization can fail at the operation, object, resolver, field, relationship, or event level.

Consider a query like:

query {
  user(id: "1001") {
    id
    name
    paymentMethods {
      lastFour
      billingAddress
    }
  }
}

The top-level user resolver may enforce access while the nested paymentMethods resolver does not.

To test this, start with an allowed query:

query {
  user(id: "1001") {
    id
    name
  }
}

Add fields the expected client does not request:

query {
  user(id: "1001") {
    id
    name
    email
    role
    internalNotes
  }
}

Then test nested relationships, fragments, aliases, interfaces, unions, deprecated fields, and alternate query shapes.

Vulnerable result: A user can retrieve protected fields or nested objects through a resolver path that lacks the expected check.

Expected result: Every protected object and field checks authorization in the relevant context. Hiding a field in the frontend is never treated as a security control.

Typical severity: High; Critical when access exposes highly sensitive data or privileged actions.

5. Test GraphQL BOLA

Broken object-level authorization (BOLA) occurs when a user can access another user's object by changing an identifier. GraphQL can expose the same object through direct queries, nested relationships, global IDs, mutations, cursors, or subscriptions, so all these need to be tested.

Representative test

Start with a sample query using account A:

query {
  invoice(id: "INV-A-100") {
    id
    total
    status
  }
}

Then replace the identifier with an object owned by account B:

query {
  invoice(id: "INV-B-200") {
    id
    total
    status
  }
}

Where present, test generic node interfaces:

query {
  node(id: "SW52b2ljZToxMDA=") {
    id
    ... on Invoice {
      total
    }
  }
}

Test read, update, delete, export, share, approve, and subscription access.

BOLA testing result matrix

Test accountRequested objectExpected result
User AUser A invoiceAllowed
User AUser B invoiceDenied
Tenant A userTenant B invoiceDenied
Standard userAdministrative objectDenied

To determine the result, do not rely only on the HTTP status – inspect both data and errors.

Vulnerable result: User A receives or modifies an object owned by user B or another tenant.

Expected result: Access is denied without revealing whether the object exists.

Typical severity: High; Critical for sensitive cross-tenant exposure, unauthorized modification, or destructive actions.

For an in-depth look at BOLA testing, see our guide to detecting BOLA in APIs.

6. Test broken function-level authorization

BOLA asks whether a user may act on a specific object, while broken function-level authorization (BFLA) asks whether the user may invoke a specific operation.

Representative test:

mutation {
  suspendUser(id: "1002") {
    id
    status
  }
}

Test the mutation as an unauthenticated user, standard user, support user, tenant administrator, and global administrator.

Vulnerable result: A low-privilege user invokes an administrative mutation or equivalent privileged operation.

Expected result: The operation is denied before sensitive resolver logic runs.

Typical severity: High; Critical when the issue enables administrative compromise or privilege escalation.

For more on BFLA detection, see our BFLA testing guide.

7. Test mutation authorization and mass assignment

Mutations combine function access, object access, workflow state, and client-controlled input.

Representative test:

mutation {
  updateUser(
    id: "1001"
    input: { displayName: "Alex" }
  ) {
    id
    displayName
  }
}

Add fields that should be server-controlled:

mutation {
  updateUser(
    id: "1001"
    input: {
      displayName: "Alex"
      role: "ADMIN"
      accountStatus: "VERIFIED"
    }
  ) {
    id
    role
    accountStatus
  }
}

Also replace the object ID and test whether the workflow can be skipped.

Vulnerable result: A user changes ownership, role, tenant, verification, approval, or workflow state without the required permission.

Expected result: The mutation authorizes both the function and object, accepts only intended fields, and enforces state transitions on the server.

Typical severity: High; Critical when privilege, ownership, or protected account state can be changed.

8. Test GraphQL injection through resolver arguments

GraphQL schema validation does not prevent a resolver from using input unsafely in SQL, NoSQL, commands, templates, paths, LDAP, or outbound requests.

Query argument:

query {
  product(search: "test'") {
    id
    name
  }
}

Mutation argument:

mutation {
  createTicket(
    input: {
      title: "Test"
      reference: "1001'"
    }
  ) {
    id
  }
}

Nested input:

mutation {
  createReport(
    input: {
      filters: {
        customerId: "1001'"
      }
    }
  ) {
    id
  }
}

Test query arguments, mutation arguments, nested inputs, IDs, custom scalars, URLs, filters, paths, and client-controlled directive arguments.

Depending on the resolver, consider SQL injection, NoSQL injection, command injection, SSRF, path traversal, LDAP injection, template injection, header injection, XML or XPath injection, and regular-expression denial of service.

Expected result: Inputs receive semantic validation, database access uses safe parameterized APIs, outbound destinations are constrained, and backend errors are not exposed.

Typical severity: High; Critical when the issue enables broad data access, remote code execution, or infrastructure compromise.

Useful manual testing tools: Burp Suite, InQL, OWASP ZAP, and controlled out-of-band interaction services.

Automated testing: Invicti tests query and mutation arguments for injection, including nested inputs, custom scalars, and ID types.

9. Test array-based batching

Some servers accept an array of operations in one HTTP request. The following is a raw JSON HTTP request body, not a GraphQL document to paste directly into a GraphiQL query pane:

[
  {
    "query": "query { account(id: \"1001\") { id } }"
  },
  {
    "query": "query { account(id: \"1002\") { id } }"
  }
]

Test maximum operations per batch, mixed queries and mutations, partial failures, body size, expensive grouped operations, and rate-limit accounting per logical operation.

Vulnerable result: One HTTP request performs many sensitive actions, bypasses rate limits, or creates disproportionate workload.

Expected result: Batching is disabled when unnecessary or constrained by operation count, cost, size, execution time, identity, and authorization.

Typical severity: Medium when batching is enabled without demonstrated impact; High when it bypasses security or resource controls.

Automated testing: Invicti detects array-based GraphQL query batching.

10. Test alias overloading

Aliases let a client repeat fields or mutations under different result names.

query {
  a: product(id: "1") { name }
  b: product(id: "2") { name }
  c: product(id: "3") { name }
}

A mutation can also be repeated:

mutation {
  a: verifyCode(code: "000001") { success }
  b: verifyCode(code: "000002") { success }
  c: verifyCode(code: "000003") { success }
}

Test whether aliases multiply resolver work or bypass anti-automation and rate limits.

Vulnerable result: Repeated aliases trigger many sensitive actions while counting as one request.

Expected result: Alias count, field breadth, operation cost, and sensitive actions are limited and counted appropriately.

Typical severity: Medium without demonstrated impact; High when a practical bypass or DoS condition is reproduced.

Automated testing: Invicti detects alias overloading DoS conditions.

11. Test query depth and GraphQL DoS controls

A deeply nested query can repeatedly traverse object relationships:

query {
  user(id: "1001") {
    manager {
      manager {
        manager {
          id
        }
      }
    }
  }
}

Circular relationship traversal can be especially expensive:

query {
  user(id: "1001") {
    team {
      members {
        team {
          members {
            id
          }
        }
      }
    }
  }
}

This test follows cyclic relationships in the schema, such as user → team → members → team. It is different from a circular fragment definition, which a conforming GraphQL validator should reject before execution.

A shallow query can still be expensive if it requests a wide selection set or repeats the same resolver through aliases. In this simplified example, the nesting depth stays low, but each alias can trigger additional work. In a real query, that cost can increase further when many fields are requested across large result sets:

query {
  p1: product(id: "1") { name }
  p2: product(id: "2") { name }
  p3: product(id: "3") { name }
}

Test pagination and result limits:

query {
  users(first: 1000) {
    nodes {
      id
    }
  }
}

Overall, assess:

  • Document and variable size
  • Fragment count
  • Field breadth
  • Alias count
  • Depth and cyclic relationship traversal
  • List and pagination limits
  • Result size
  • Expensive resolvers
  • Total query cost
  • Execution timeouts

Expected result: The server applies layered limits that reflect real resolver behavior rather than relying on one generic threshold.

Typical severity: Medium for missing limits; High when practical service degradation is demonstrated.

Useful tools: graphql-cop can help identify common GraphQL configuration and demand-control weaknesses. A proxy or GraphQL client is still needed for controlled depth, breadth, and cost testing.

Automated testing: Invicti detects circular-query DoS conditions and unchecked query length.

12. Test for CSRF and accepted content types

Cookie-authenticated GraphQL endpoints can be exposed to cross-site request forgery (CSRF) if they accept cross-origin requests that browsers can send without a CORS preflight, such as form-encoded or plain-text POST requests. Requiring application/json for state-changing POST requests reduces exposure to browser-simple CSRF requests, but it's not sufficient if the server also allows state-changing operations through GET or accepts other simple content types.

SameSite cookies provide another layer of protection. Use SameSite=Strict for session cookies where application flows permit it, or Lax where cross-site top-level navigation is required. Test actual browser behavior rather than relying on the attribute alone, especially when SameSite is omitted and the browser applies Lax handling by default.

Representative mutation:

mutation {
  updateEmail(input: { email: "new@example.com" }) {
    success
  }
}

Equivalent form-encoded request:

Content-Type: application/x-www-form-urlencoded

query=mutation+%7B+updateEmail(input%3A+%7Bemail%3A+%22new%40example.com%22%7D)+%7Bsuccess%7D+%7D

Check cookie authentication, accepted content types, GET behavior, SameSite settings, CSRF tokens, custom headers, Origin and Referer validation, and CORS.

Read-only queries over GET are not automatically a CSRF vulnerability, but queries with side effects, sensitive cached responses, or unsafe resolver behavior still require review.

Vulnerable result: A cross-origin browser request performs a meaningful mutation using the victim's authenticated session.

Expected result: State-changing operations require an explicit CSRF defense, unnecessary content types are rejected, and mutations cannot execute through GET.

Typical severity: High when a meaningful state-changing action is possible.

Automated testing: Invicti detects CSRF exposure through non-JSON GraphQL queries and mutations.

13. Test error leakage

Submit syntax errors, type mismatches, nonexistent IDs, and controlled resolver failures, for example:

query {
  user(
}
query {
  user(id: true) {
    id
  }
}

Review responses for stack traces, source paths, framework versions, database errors, internal hostnames, resolver names, backend statements, secrets, tokens, and object-existence clues.

Expected result: Clients receive stable, sanitized errors while detailed diagnostics remain in protected logs.

Typical severity: Medium; higher when errors expose secrets, sensitive data, or a direct exploit path.

Automated testing: Invicti detects GraphQL error leakage and stack-trace disclosure.

14. Test uploads, custom scalars, and directives

A GraphQL upload may look like:

mutation UploadAvatar($file: Upload!) {
  uploadAvatar(file: $file) {
    filename
  }
}

Test file size, count, actual content type, filename handling, storage location, malware scanning, parser limits, cleanup, ownership, retrieval authorization, and CSRF protection.

Multipart upload formatting is implementation-specific, so the full request should follow the server's documented upload convention.

Testing custom scalars

A URL scalar might pass data to a server-side fetcher:

mutation {
  importFromUrl(url: "http://127.0.0.1:8080/admin") {
    status
  }
}

A JSON scalar may allow unexpected nested fields:

mutation {
  updateSettings(
    input: {
      config: {
        role: "admin"
      }
    }
  ) {
    success
  }
}

Review custom scalars such as URL, JSON, UUID, Upload, HTML, or domain-specific IDs. A scalar name does not guarantee strict validation.

Testing directives

Standard directives such as @include and @skip normally affect field selection:

query {
  account(id: "1001") @include(if: true) {
    id
    balance
  }
}

Custom directives may influence authorization, caching, transformation, or resolver execution. Test whether:

  • Directive arguments receive semantic validation
  • Directive placement changes authorization behavior
  • Internal directives are exposed through introspection
  • Equivalent queries behave differently with and without a directive
  • Directives alter caching or execution in security-relevant ways

There is no universal directive-bypass payload – testing needs to follow the directives implemented by the target schema.

Expected result: Uploads, custom scalars, and directives receive explicit validation and authorization appropriate to how the resolver uses them.

Typical severity: Medium; High when the behavior enables unauthorized access, SSRF, unsafe file handling, or another exploitable condition.

15. Test GraphQL subscription security and WebSocket controls

GraphQL subscriptions often use long-lived WebSocket connections with separate connection, subscription, and event-delivery stages.

Start with a legitimate object-scoped subscription, then replace the identifier with one belonging to another controlled user or tenant to test whether subscription and event delivery authorization are enforced:

subscription {
  paymentUpdated(accountId: "ACC-1001") {
    id
    status
  }
}

Cross-site WebSocket hijacking can occur when a browser opens an authenticated WebSocket connection from an untrusted origin, typically because the upgrade request uses ambient cookies and the server does not validate the Origin header.

What to test for:

  • Authentication during connection setup
  • Authorization for each subscription
  • Authorization when each event is delivered
  • Token expiry and permission changes
  • Origin validation
  • Cross-site WebSocket hijacking
  • Connection and subscription limits
  • Message size and lifetime limits
  • Reconnection and cleanup
  • Error leakage

Vulnerable result: A user receives another user's events, retains access after permission changes, or opens an authenticated socket from an untrusted origin.

Expected result: The server authenticates the connection, authorizes every subscription and event, enforces origin policy, and responds to permission changes during long-lived sessions.

Typical severity: High; Critical for sensitive cross-user or cross-tenant event exposure.

Useful tools: Use a WebSocket-capable interception proxy to inspect connection setup, subscription messages, token handling, and event delivery.

16. Test persisted queries and operation allowlisting

Persisted queries and trusted documents reduce the range of accepted operations but do not replace authorization or demand controls.

Start with a known persisted operation, then change the hash, remove the document, or supply a full query alongside an unknown hash to verify whether the server truly enforces the allowlist and handles fallback behavior safely:

{
  "operationName": "GetInvoice",
  "variables": {
    "id": "INV-A-100"
  },
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "example-hash"
    }
  }
}

Vulnerable result: Arbitrary or deprecated operations execute despite claimed allowlisting.

Expected result: Only approved operations execute where allowlisting is required, while runtime authorization and variable validation remain in place.

Typical severity: Medium; High when bypass exposes restricted functionality or weakens demand controls.

GraphQL security best practices for production

Authorization

  • Deny access by default
  • Authorize every protected operation, object, field, and event
  • Enforce ownership and tenant boundaries
  • Add multi-user regression tests

Input and resolver security

  • Validate according to business meaning
  • Use parameterized database access
  • Constrain outbound destinations
  • Map permitted mutation fields explicitly
  • Validate custom scalars, directives, and uploads

Demand control

  • Limit depth, cycles, aliases, fields, batches, documents, and variables
  • Enforce pagination and result limits
  • Calculate query cost where appropriate
  • Apply identity-aware rate limits
  • Set execution timeouts
  • Consider persisted or trusted operations

Exposure and monitoring

  • Review introspection and field suggestion policy
  • Remove or restrict development consoles
  • Remove unused schema elements
  • Restrict methods and content types
  • Sanitize production errors
  • Log identity, duration, cost, outcome, and a stable operation identifier
  • Detect unusual batching, aliases, depth, mutations, and subscriptions

For logging, do not rely on operationName alone. It is client-supplied and optional, so unnamed operations can create blind spots. Use a normalized document hash, persisted-operation ID, or another stable server-side identifier alongside any supplied operation name.

Using manual and automated GraphQL API security testing together

Manual testing is essential for business rules, role comparisons, multi-step workflows, BOLA, and subscription context. Automated dynamic application security testing (DAST) complements that work by repeatedly exercising running GraphQL APIs for vulnerabilities and insecure behavior.

Invicti's API DAST can scan GraphQL APIs using an imported schema or introspection-based discovery, then exercise supported operations as part of authenticated or recurring DAST scans. This automates repeatable checks while leaving business-context authorization decisions to manual testing.

The right balance of manual and automated testing depends on how much application context each test requires. Automated scanning is well suited to repeatable technical checks, while authorization and workflow testing often require controlled users, roles, tenants, and known object relationships.

GraphQL test areaRecommended approachWhere Invicti's API DAST helps
Endpoint and library detectionAutomate discovery, then verify the exposed service and implementationDetects GraphQL endpoints and supported libraries
GraphiQL and Playground exposureAutomate detection and review whether the interface should be available in the target environmentDetects exposed GraphiQL Explorer and Playground interfaces
IntrospectionDetect automatically, then assess against the organization's production policyDetects enabled introspection
Field suggestionsDetect automatically and review whether schema hints expose unnecessary informationDetects field suggestions and auto-correct behavior
Unauthenticated mutationsAutomate initial testing, then confirm business impact manuallyDetects mutations that can be invoked without authentication
Cross-user and cross-tenant BOLAUse controlled accounts and known object identifiers to compare access across users and tenantsManual validation is essential because the expected ownership model is application-specific
Field-level and nested resolver authorizationCompare responses across roles, query shapes, and nested relationshipsManual validation is essential because field visibility and resolver rules depend on business context
Injection through resolver argumentsAutomate broad input coverage, then investigate confirmed behavior in contextTests query and mutation arguments, including nested inputs, custom scalars, and ID types
Array-based batchingAutomate detection, then test rate limiting and aggregate execution controls under controlled conditionsDetects array-based query batching
Alias overloadingAutomate detection and validate performance impact safelyDetects alias overloading DoS conditions
Circular relationship traversalAutomate detection and assess depth, cost, and execution controls safelyDetects circular-query DoS conditions
Unchecked query lengthAutomate detection and verify whether limits are enforced consistentlyDetects missing or ineffective query-length controls
CSRF through non-JSON requestsAutomate request-format testing, then verify browser behavior and cookie handlingDetects CSRF exposure through non-JSON queries and mutations
Error leakage and stack tracesAutomate detection and review whether returned details expose internal implementation dataDetects verbose GraphQL errors and stack-trace disclosure
Subscription authorizationTest with controlled users and tenants, including authorization at connection, subscription, and event-delivery stagesManual validation is essential because event scope and entitlement rules are application-specific

Tips for testing in CI/CD pipelines:

  • Run fast, low-impact checks in pull requests
  • Run authenticated scans in staging
  • Reserve heavier demand-control testing for controlled scheduled scans
  • Block on confirmed critical or high findings
  • Do not block solely because introspection is enabled unless policy requires it

Downloadable GraphQL security testing checklist

GraphQL security testing checklist

Build GraphQL security testing into the development lifecycle

A GraphQL assessment should produce more than isolated findings. Convert confirmed issues into authorization regression tests, secure resolver patterns, reusable DAST checks, updated inventories, monitoring rules, and retest requirements.

The strongest programs combine manual context with repeatable runtime testing. Manual testers establish what each user should be allowed to do, while automated testing verifies that common weaknesses and regressions do not return as the schema and resolver layer change.

Next steps

Frequently asked questions

Frequently asked questions about GraphQL security testing

What should a GraphQL security testing checklist include?

It should cover endpoint discovery, schema exposure, authentication, resolver and field authorization, BOLA, privileged operations, mutation security, injection, batching, aliases, query depth, complexity, CSRF, errors, uploads, subscriptions, persisted queries, and production monitoring.

How do GraphQL batching and aliases affect rate limiting?

One HTTP request can contain multiple logical operations through batching or aliases. A gateway that counts only HTTP requests may underestimate authentication attempts, mutations, or backend workload. Rate limits should account for identity, operation count, and total query cost.

Can DAST automate GraphQL security testing?

DAST can automate repeatable runtime checks such as introspection, field suggestions, batching, injection, query limits, CSRF, error leakage, and exposed development tooling. Manual testing remains necessary for business logic, multi-user authorization, BOLA, and subscription context.

Table of Contents