Security Glossary

GraphQL Injection

An attacker-controlled GraphQL argument value flowing unsanitized into a resolver's SQL, NoSQL, OS, or backend call, because the schema types the argument but never sanitizes its contents.

GraphQL Injection occurs when an attacker-controlled argument value flows unsanitized from a GraphQL query into a backend call inside a resolver: a SQL or NoSQL query, an OS command, or another downstream request. GraphQL validates the shape of a query against the schema, but it does nothing to sanitize the values a resolver passes on. If the resolver builds a query by string concatenation, the classic injection classes apply unchanged.

The Core Problem

A field argument declared as a String is still just a string. The schema guarantees the type, not the safety of its contents. Injection lives entirely in how the resolver uses that value.

SQL Injection Through a Resolver

# Schema
type Query { user(id: String!): User }

# Vulnerable resolver: argument concatenated into SQL
def resolve_user(id:)
  User.connection.execute(
    "SELECT id, email FROM users WHERE id = '#{id}'"
  )
end

# Attacker query supplies SQL in the argument value
query {
  user(id: "1' OR '1'='1") {
    email
  }
}
# Resolver runs: SELECT id, email FROM users WHERE id = '1' OR '1'='1'

The fix is the same as anywhere else: parameterize. Bind the argument as a value (WHERE id = ?) instead of interpolating it into the query string.

NoSQL and Other Sinks

# A filter argument passed straight into a Mongo query becomes an
# operator injection when the resolver forwards the structure verbatim:
query {
  users(filter: "{\"email\": {\"$ne\": null}}") { email }
}
# Resolver: collection.find(JSON.parse(filter))  -> matches every user

# The same pattern injects into OS commands, LDAP filters, or
# downstream HTTP calls whenever a resolver interpolates the argument.

Prevention

  • Use parameterized queries and safe driver APIs in resolvers; never concatenate argument values into SQL, NoSQL, shell, or LDAP strings
  • Validate and constrain argument values (type, format, allowlist) even though the schema already typed them
  • Treat every resolver argument as untrusted input regardless of its declared GraphQL type

Related but Distinct

Requesting sensitive fields like password or isAdmin is a field-authorization / excessive-data-exposure problem, not injection. Schema reconnaissance is covered by GraphQL Introspection, and abusing query depth or batching for denial of service is covered by GraphQL Batching Attack.

See Also