GraphQL Introspection Skill
Purpose
Performs GraphQL introspection queries to discover schema, types, queries, mutations, and subscriptions. Assesses schema completeness and identifies security concerns.
Detection Strategy
Step 1: Identify GraphQL Endpoint
Test these paths with a POST request:
| Path | Notes |
|---|---|
/graphql | Most common |
/gql | Short form |
/api/graphql | Namespaced |
/v1/graphql | Versioned |
/graphql/v1 | Alt versioned |
/query | Hasura style |
Step 2: Test Introspection
Send the introspection query:
{
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
name
kind
description
fields {
name
description
type {
name
kind
ofType {
name
kind
}
}
args {
name
type {
name
kind
}
}
}
}
directives {
name
description
}
}
}
HTTP Request:
POST /graphql HTTP/1.1
Content-Type: application/json
{
"query": "{ __schema { queryType { name } mutationType { name } subscriptionType { name } types { name kind description fields { name description type { name kind ofType { name kind } } args { name type { name kind } } } } directives { name description } } }"
}
Step 3: Parse Schema
Extract the following from introspection results:
{
"has_graphql_schema": true,
"introspection_enabled": true,
"endpoint": "/graphql",
"schema": {
"queries": [
{
"name": "getPayment",
"args": ["id: ID!"],
"return_type": "Payment"
},
{
"name": "listPayments",
"args": ["limit: Int", "offset: Int"],
"return_type": "[Payment]"
}
],
"mutations": [
{
"name": "createPayment",
"args": ["input: PaymentInput!"],
"return_type": "Payment"
}
],
"subscriptions": [
{
"name": "onPaymentStatusChange",
"args": ["paymentId: ID!"],
"return_type": "PaymentEvent"
}
],
"types": {
"custom": 25,
"input": 10,
"enum": 5,
"interface": 2,
"union": 1
},
"total_fields": 150,
"total_queries": 15,
"total_mutations": 8,
"total_subscriptions": 3
}
}
Security Assessment
Introspection Enabled (Security Risk)
If introspection is enabled in production:
{
"security_concerns": [
{
"severity": "medium",
"issue": "GraphQL introspection enabled in production",
"description": "Introspection exposes the complete API schema including all types, queries, and mutations. This can aid attackers in understanding the API surface.",
"recommendation": "Disable introspection in production. Use schema documentation tools instead."
}
]
}
Other Security Checks
| Check | Risk | Description |
|---|---|---|
| Introspection enabled | Medium | Schema exposure |
| No query depth limit | High | DoS via nested queries |
| No query complexity limit | High | Resource exhaustion |
| Mutations without auth | Critical | Unauthorized data modification |
| PII in type names | Low | Information disclosure |
Schema Analysis
Type Classification
Filter out built-in GraphQL types:
BUILTIN_TYPES = {"String", "Int", "Float", "Boolean", "ID",
"__Schema", "__Type", "__Field", "__InputValue",
"__EnumValue", "__Directive", "__DirectiveLocation"}
custom_types = [t for t in types if t["name"] not in BUILTIN_TYPES
and not t["name"].startswith("__")]
Complexity Score
def schema_complexity(schema):
types = len(schema["types"]["custom"])
queries = schema["total_queries"]
mutations = schema["total_mutations"]
fields = schema["total_fields"]
if fields > 500 or types > 100:
return "high"
elif fields > 100 or types > 30:
return "medium"
return "low"
Output Format
{
"has_graphql_schema": true,
"introspection_enabled": true,
"endpoint": "https://api.example.com/graphql",
"schema_summary": {
"total_queries": 15,
"total_mutations": 8,
"total_subscriptions": 3,
"custom_types": 25,
"total_fields": 150,
"complexity": "medium"
},
"security_concerns": [],
"top_queries": ["getPayment", "listPayments", "getAccount"],
"top_mutations": ["createPayment", "updatePayment"]
}