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.
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.
# 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.
# 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.
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.