Join our newsletter

Operation Name Security in GraphQL

Operation Name Security in GraphQL

Shahar Binyamin & Dolev Farhi·

Operation Names in GraphQL are an optional string a client can specify to describe the operation, also called Named Operation (Section 5.2.1) of the GraphQL specification.

The specification outlines the following rules as they relate to Operation Names:

  • For each operation definition operation in the document.
  • Let operationName be the name of operation.
  • If operationName exists -- Let operations be all operation definitions in the document named operationName. -- operations must be a set of one.

This means that a document with more than one query or mutation must include operation names for each, and they must be uniquely named.

For example, the following document is valid:

query First {
  pastes {
     content 
  }
}

query Second {
  pastes {
     content 
  }
}

And this document is invalid because two queries use the same operation name, violating the uniqueness requirement:

query First {
  pastes {
     content 
  }
}

query First {
  pastes {
     content 
  }
}

This document is also invalid because only one query has a defined operation name in the document:

query First {
  pastes {
     content 
  }
}

query {
  pastes {
     content 
  }
}

When a request is sent to a GraphQL API with more than one operation, a client would need to specify the operationName JSON key with the specific named operation of interest to run it:

{
  "query":"query First { pastes { content } } query Second { pastes { content } }", 
  "operationName":"First", 
  "variables":[]
}

If the operationName JSON key is not defined but the query document contains two named operations, the GraphQL server may respond with the following:

{
  "errors": [
    {
      "message": "Must provide operation name if query contains multiple operations.",
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ]
}

So, operation names are optional if a document contains one operation, but once it contains more than one, operation names are required and must be unique (i.e. not repeated).

Applications may log the operation names used by client queries for analytics and debugging. They are used by analytic tools to determine, for example, performant and non-performant queries and often are used instead of logging the entire query payload.

Have you ever asked yourself: what characters can be used in a named operation? Let’s see what is accepted versus not accepted with some Python-fu.

Operation Name Structure.png

Experimenting with Operation Name Characters

For the purpose of this test, we used Sangria, Apollo, graphql-ruby, Graphene and HyperGraphQL as the targeted GraphQL servers a mix of seasoned and newer implementations, and used the following script to test various combination of operation name characters, while removing ignored tokens such as hashes (#) commas (,) and whitespace.

import string
import time
import random
import requests

def randomize(N):
  random_int = random.randint(1, 7)
  strings = {1: string.ascii_lowercase + string.digits,
             2: string.ascii_lowercase + random.choice(string.punctuation),
             3: string.digits + random.choice(string.punctuation),
             4: string.punctuation,
             5: string.ascii_lowercase,
             6: string.digits,
             7: string.printable}

  chosen = strings[random_int]
  chosen = chosen.replace(' ', '')
  chosen = chosen.replace('#', '')
  chosen = chosen.replace(',','')
  chosen = random.sample(chosen, len(chosen))


  return ''.join(random.choice(chosen) for _ in range(N))


while True:
  opName = randomize(5)
  query = """
    query {0} {{
       __typename
    }}""".format(opName)


  r = requests.post('http://test.inigo.local', json={"query":query})

  if r.ok:
      print(f'Accepted: {opName}')
  else:
      print(f'Not accepted: {opName}')

Running the code produces the following results:

Accepted: k4qcb
Accepted: lzort
Not accepted: ]}VKV
Not accepted: 69071
Not accepted: \|&!~
Accepted: fclue
Not accepted: 2kxq6
Not accepted: $&${}
Accepted: _5157
Not accepted: +_'!)
Accepted: ztb0v

Tests Breakdown

Experimenting with Operation Names.png

Symbols such as greater than (>) or less than (<) typically used in Cross-Site Scripting payloads aren’t allowed, neither single-quotes or double quotes, equal sign, and others in most cases. One anomaly was HyperGraphQL, a Java-based GraphQL server, which allowed characters such as: `+><;^%~+ as operation name.

Operation Name HyperGraphQL.png

This was an interesting experiment. Despite the specification describing what characters should and should not be interpreted, there are deviations from the norm. This is why it is important to know your implementation well.

With Inigo, you can define strict rules on what operation names (as well as other components of the document) to accept from clients, so no matter what GraphQL servers you use, your policy is strict across all of them.

kind: Security
name: demo
label: starwars
spec:
  validation:
    alias_name: "^[a-zA-Z]+$"
    directive_name: "^[a-zA-Z]+$"
    operation_name: "^[a-zA-Z]+$"
    arguments:
      String: "^[a-zA-Z]+$"

Inigo can help you enforce the use of operation names across all your queries, in addition to applying a strict security policy so that operation names are limited to a safer subset of characters. Reach out to us for more information on how we can enforce security policies across all your GraphQL fleet