$npx -y skills add utkusen/sast-skills --skill sast-graphqlDetect GraphQL injection vulnerabilities in a codebase using a three-phase approach: recon (confirm GraphQL usage and find unsafe operation document assembly sites), batched verify (trace user input to those sites in parallel subagents, up to 3 candidate sites each), and merge (c
| 1 | # GraphQL Injection Detection |
| 2 | |
| 3 | You are performing a focused security assessment to find GraphQL injection vulnerabilities. This skill uses a three-phase approach with subagents: **recon** (confirm GraphQL usage and find every location where a GraphQL operation document is assembled unsafely), **batched verify** (trace whether user-supplied input reaches those assembly sites, in parallel batches of up to 3 sites each), and **merge** (consolidate batch results into the final report). |
| 4 | |
| 5 | **Prerequisites**: `sast/architecture.md` must exist. Run the analysis skill first if it doesn't. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## What is GraphQL Injection |
| 10 | |
| 11 | GraphQL injection occurs when user-controlled data is embedded into the **GraphQL document** (the query, mutation, or subscription string) rather than passed only through the **variables** map. The parser then interprets attacker-controlled syntax — new fields, aliases, directives, or fragments — which can bypass intent, reach unauthorized resolvers, or change server-side behavior when that document is executed or forwarded. |
| 12 | |
| 13 | The core pattern: *unvalidated user input alters the structure or text of the GraphQL operation string passed to `execute`, `graphql`, a gateway client, or an HTTP body `query` field built from string operations.* |
| 14 | |
| 15 | ### What GraphQL Injection IS |
| 16 | |
| 17 | - Concatenating or interpolating user input into an operation string: `` `query { user(id: "${id}") { name } }` ``, `"query { user(id: \"" + id + "\") { name } }"` |
| 18 | - Building the JSON `query` field for a downstream GraphQL HTTP request with string concat from request body or params |
| 19 | - Forwarding `req.body.query` (or similar) into another interpolated template that wraps or extends the operation |
| 20 | - Dynamic `gql` / `graphql-tag` template literals where a non-static expression changes document structure (not just a bound variable value inside a static document) |
| 21 | - Server-side code that selects or assembles operation text from user input (including "persisted query" ID → document maps without allowlisting) |
| 22 | - Wrappers around `graphql.execute()`, `graphqlHTTP`, Yoga/Apollo request pipeline where the first argument (document/source) is built from variables that could be user-influenced |
| 23 | |
| 24 | ### What GraphQL Injection is NOT |
| 25 | |
| 26 | Do not flag these as GraphQL injection: |
| 27 | |
| 28 | - **SQL injection in resolvers**: Resolver code that builds SQL from `args` — that is **SQL injection** (`sast-sqli`), not this skill |
| 29 | - **NoSQL / command injection in resolvers**: Same — use the appropriate SAST skill |
| 30 | - **IDOR via GraphQL arguments**: Passing another user's ID in a **variables** JSON with a **static** document — authorization flaw, not document injection |
| 31 | - **Normal variable binding**: Static document with `{"query": "query($id: ID!) { user(id: $id) { name } }", "variables": {"id": userInput}}` — values are bound as variables; the document structure is fixed (still verify authorization in resolvers) |
| 32 | - **Introspection / field suggestion enabled**: Information disclosure and hardening topic; only flag as GraphQL injection if the finding is specifically about **injecting into the operation string** |
| 33 | - **Query depth / complexity DoS**: Rate limiting and cost analysis — different class |
| 34 | |
| 35 | ### Patterns That Prevent GraphQL Injection |
| 36 | |
| 37 | **1. Static operation documents with variables** |
| 38 | |
| 39 | ```javascript |
| 40 | const GET_USER = gql` |
| 41 | query GetUser($id: ID!) { |
| 42 | user(id: $id) { name } |
| 43 | } |
| 44 | `; |
| 45 | // execute(schema, GET_USER, null, context, { id: userId }); |
| 46 | ``` |
| 47 | |
| 48 | **2. Server uses standard HTTP handler; client sends document; server parses once** |
| 49 | |
| 50 | The risk is not the mere presence of `req.body.query` on the server if the server only parses and executes it as the client's operation — injection in *that* path is client-side. Flag **server-side** construction of a **new** document that incorporates user strings before `execute` or before forwarding. |
| 51 | |
| 52 | **3. Persisted queries / allowlisted operation IDs** |
| 53 | |
| 54 | Document looked up by ID from a server-side registry; client cannot inject arbitrary document text. |
| 55 | |
| 56 | **4. graphql-js `Source` with static string; dynamic values only in variableValues** |
| 57 | |
| 58 | ```javascript |
| 59 | graphql({ schema, source: staticQueryString, variableValues: { id: userId } }); |
| 60 | ``` |
| 61 | |
| 62 | --- |
| 63 | |
| 64 | ## Vulnerable vs. Secure Examples |
| 65 | |
| 66 | ### Node.js — dynamic document for downstream API |
| 67 | |
| 68 | ```javascript |
| 69 | // VULNERABLE: user input in operation text |
| 70 | app.post('/proxy', async (req, |