Discovery & engine fingerprinting
// tools: discover_graphql · fingerprint_graphqlThe endpoint is found first, then the operation surface behind it. Every root query, mutation and subscription becomes a scannable endpoint in its own right, so the rest of the scan tests operations rather than a single URL. The engine is fingerprinted from its error dialect — the way a server phrases a malformed-query rejection is distinctive — which selects the version-specific CVEs and the default-on features worth probing.
// MOUNT SWEEP + SURFACE ENUMERATION
// ENGINE DETECTION (GRAPHW00F-STYLE)
Malformed and edge-case probes are matched against known error-message signatures. The detected engine is recorded so its known CVEs and default-on behaviours (batching, suggestions, an IDE mount) can be checked specifically rather than generically.
The misconfiguration sweep
// tool: test_graphqlSeven checks that only exist in GraphQL, run against a confirmed endpoint. Each has its own reproduce-first guard — a hardened server stays silent on all seven.
// INTROSPECTION ENABLED medium
The complete type system — every type, field and argument — handed to an anonymous caller. Recorded only when data.__schema actually comes back, never on a 200 alone.
// FIELD SUGGESTIONS medium
Suggestions rebuild the schema field by field even with introspection switched off — the "clairvoyance" technique. Probed with both a random field and a deliberate typo of a real one, so a server that only leaks on near-misses is still caught.
// QUERY BATCHING medium
One HTTP request carrying hundreds of operations defeats per-request rate limiting: a login brute-force amplifier and a cheap DoS lever. Flagged only when the server genuinely executes the batch.
// NO DEPTH / COMPLEXITY LIMIT medium
Schema-aware: the nesting is built from an object cycle the target actually has, and the aliases from a real list field — no hardcoded field names, so it works on any engine. Fires if either query is accepted with no depth, complexity or alias rejection.
// VERBOSE RESOLVER ERRORS medium
Two probes. First a crash query; if that stays quiet, an unbalanced quote is sent into each real String argument in the schema — string interpolation then leaks the database engine and the statement itself. Recorded only on a language stack trace or a database-engine error that echoes the raw query. A hardened server returns a generic message and never flags.
// GRAPHQL CSRF medium — AND WHY IT DOESN'T FALSE-POSITIVE
A state-changing mutation executed over a GET or form-encoded "simple request" using only the cookies a browser sends automatically is forgeable from any site your users visit. Two guards keep this honest, both learned from real false positives:
Injection & SSRF through resolvers
// tools: test_graphql_injection · test_graphql_ssrfThe classic classes are still there — they just live behind a resolver argument instead of a query string, which is exactly why generic scanners miss them. NewScan introspects the schema, builds a valid query per field, and injects into the arguments that actually exist, reusing the same false-positive-safe oracles as the HTTP detectors: an evaluated arithmetic product or a command-output marker, never an echoed payload.
// SQL & COMMAND INJECTION high–critical
// BLIND SSRF VIA A MUTATION high
A resolver that fetches a URL for you is an SSRF primitive. NewScan points it at an OOB collaborator canary and records only on a real callback — with no collaborator configured the probe is inert, so an offline scan cannot produce this false positive.
What gets recorded
// schema leak · DoS · injection| Finding | How NewScan confirms it | Severity |
|---|---|---|
| Injection via resolver | Command-output marker or an error-based SQL signal from an introspected argument — reproduced. | high–critical |
| GraphQL SSRF | A mutation fetches an attacker-supplied URL; confirmed on an out-of-band callback. | high |
| Introspection enabled | data.__schema returned to an anonymous caller. | medium |
| Field suggestions | A "Did you mean" response rebuilds the schema with introspection off. | medium |
| Query batching | An array of operations is executed in one request — rate-limit bypass. | medium |
| No depth/complexity limit | A schema-derived deep-nested or alias-amplified query is accepted. | medium |
| GraphQL CSRF | A mutation executes over GET/form-encoded with ambient cookies only. | medium |
| Verbose errors | A stack trace or database-engine error echoing the raw query. | medium |
| IDE / playground exposed | GraphiQL or a playground console is served to anonymous callers. | low |
Measured, not asserted
// against a deliberately vulnerable GraphQL appThese detectors are scored against Damn Vulnerable GraphQL Application and a synthetic offline benchmark on every change. The benchmark is the floor: full recall, zero false positives — a change that flags a safe control fails the build, so coverage never grows by getting noisier. The GraphQL work that closed DVGA's list/object-field SQLi, verbose-error disclosure and missing depth limits shipped only once the offline gate still read clean.
How it's recorded
// audit-ready evidenceCATEGORY
Injection maps to OWASP API8; SSRF to API7; schema and error leaks to A01/CWE-200.
SEVERITY
One calibrated severity per finding that translates into each framework's risk rating and remediation SLA — no inflated numbers.
EVIDENCE
The exact operation document, the response that proved it, and — for SSRF — the out-of-band callback that confirmed it.
Point it at your schema.
NewScan is free and self-hosted — bring your own key. Every check on this page runs in the deterministic floor with no model or provider key at all; only the blind-SSRF confirmation needs an OOB collaborator.