You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Production hardens GraphQL by disabling introspection (api_platform.yaml when@prod: introspection.enabled:false) and graphiql. However introspection disabling only blocks __schema/__type meta-queries; it does not disable webonyx/graphql-php's document-validation suggestions. The FieldsOnCorrectType / fields-and-types validation rules run regardless and emit "Cannot query field X on type Y. Did you mean Z?" and type-name suggestions. API Platform 4.1 exposes these validation error messages verbatim on the public POST /api/graphql endpoint with no custom error formatter registered to strip them (no formatError/error-handler override exists anywhere in config or src/Shared). An attacker can thus reconstruct field and type names one probe at a time even though introspection appears locked down, defeating the intended controls.
Severity: LOW • Category: A05:2021 Security Misconfiguration (information disclosure) • OWASP: A05:2021; API8:2023 Found by an authorized automated adversarial pentest loop and confirmed by 2 independent skeptic verifiers (unanimous).
Affected code / location
config/packages/api_platform.yaml:8-11,49-53 (graphiql:false, introspection.enabled:false in prod, no error/validation sanitization); config/packages/security.yaml:16 (^/api/graphql POST PUBLIC_ACCESS)
Exploit scenario
POST to /api/graphql (public) a query referencing a deliberately misspelled field, e.g. { userr { ide } }. 2. The response validation error returns Did you mean "user"? / Did you mean "id"?, leaking valid field names. 3. Iterate the guess-and-suggest oracle across types/fields/mutations to rebuild the schema (mutation names, argument fields) without ever calling __schema — enabling targeted attacks against operations that assume schema opacity in prod.
Suggested remediation
Register a production GraphQL error/validation handler that suppresses field/type suggestion text (or disables the QuerySuggestion behavior in webonyx) so validation errors return generic messages, consistent with introspection being off.
Verification notes
Verifier 1 (CONFIRMED, exploitable=true, duplicate=false): Traced a concrete, currently-reachable exploit path. (1) api_platform.yaml:49-53 disables only introspection.enabled and graphiql in prod; with webonyx/graphql-php ^15.17 this installs the DisableIntrospection rule, which blocks __schema/__type only. The FieldsOnCorrectType validation rule still runs and emits "Cannot query field X on type Y. Did you mean Z?" suggestions; graphql-php has no toggle for these and no custom validation rule is registered (grep of config/ and src/Shared shows none). (2) security.yaml:16 makes POST /api/graphql PUBLIC_ACCESS, so probing needs no auth. (3) No formatter strips validation-error messages: DomainExceptionNormalizer (DomainExceptionNormalizer.php:54) only rewrites GraphQL Error objects whose getPrevious() is a DomainException — pre-execution validation errors have no such previous and pass through the default normalizer verbatim; ErrorProvider (ErrorProvider.php:99-101) only substitutes a generic message for GraphQL when status >= 500 (execution-phase exceptions routed through the Error resource), and validation errors never reach it. Thus field/type/mutation names leak via the "Did you mean" oracle despite introspection being disabled. Not in the KNOWN list ([Security] critical: OAuth2/JWT access tokens silently escalate to ROLE_SERVICE; issuer/audience validation skipped; session-revocation bypassed #312-[Security] low: Account-lockout failure counter uses non-atomic read-modify-write (race allows extra guesses) #324 are unrelated). Severity low is appropriate: the oracle only confirms near-miss guesses (Levenshtein-style suggestionList), a weaker leak than full introspection, but it genuinely partially defeats the intended control and matches the OWASP GraphQL "disable field suggestions in production" hardening guidance.
Verifier 2 (CONFIRMED, exploitable=true, duplicate=false): Verified against actual code. config/packages/api_platform.yaml:49-53 disables only graphql.introspection.enabled in prod (the DisableIntrospection rule blocks __schema/__type only); graphiql:false disables the UI. There is NO custom error formatter, ErrorHandlerInterface, ExecutorInterface, or ValidationRule decoration anywhere: grep of config/ and src/ for formatError/setErrorFormatter/serializeError/suggestion/ValidationRule/ExecutorInterface returned nothing GraphQL-relevant (only rate-limit resolvers and GraphQLBatchRejectListener). src/Shared/Application/Provider/ErrorProvider.php sanitizes GraphQL runtime 500s (provideInternalServerError returns static 'error.internal') but NOT document-validation errors, which graphql-php produces before resolvers run and returns verbatim in errors[]. composer.lock pins webonyx/graphql-php v15.32.3, whose default FieldsOnCorrectType/KnownTypeNames/KnownArgumentNames rules emit 'Did you mean X?' suggestions with no repo config to disable them. security.yaml:16 makes POST ^/api/graphql PUBLIC_ACCESS and validation runs pre-authorization, so the guess-and-suggest oracle is reachable unauthenticated in prod. This is the residual channel introspection-disable does not cover, distinct from known fixed issues [Security] critical: OAuth2/JWT access tokens silently escalate to ROLE_SERVICE; issuer/audience validation skipped; session-revocation bypassed #312-[Security] low: Account-lockout failure counter uses non-atomic read-modify-write (race allows extra guesses) #324 ([Security] high: GraphQL endpoint bypasses all per-endpoint rate limiters (sign-in, refresh, password-reset, 2FA) #315 covered GraphQL rate limiting, not error disclosure). Mitigating context that lowers impact but does not eliminate the finding: security.yaml:11 exposes ^/api/docs as PUBLIC_ACCESS with no enable_docs:false override, so REST/OpenAPI schema for shared resources is already public; however custom GraphQL mutation resolvers (2FA, refresh-token, sign-out) expose GraphQL-specific field/argument names not fully present in OpenAPI, so the suggestion oracle still leaks genuine non-public naming. Real, currently reachable, low severity, not a duplicate.
Related
Part of the enterprise security-hardening effort — umbrella tracker #348; security NFR issues #426 (securability), #441 (vulnerability), #362 (confidentiality), #389 (integrity). Prior security wave: #312–#324. Not a duplicate of the already-fixed items in that range.
Acceptance = the exploit path is closed AND a regression test (unit/Behat/Schemathesis) proves it stays closed.
Summary
Production hardens GraphQL by disabling introspection (api_platform.yaml when@prod: introspection.enabled:false) and graphiql. However introspection disabling only blocks __schema/__type meta-queries; it does not disable webonyx/graphql-php's document-validation suggestions. The FieldsOnCorrectType / fields-and-types validation rules run regardless and emit "Cannot query field X on type Y. Did you mean Z?" and type-name suggestions. API Platform 4.1 exposes these validation error messages verbatim on the public POST /api/graphql endpoint with no custom error formatter registered to strip them (no formatError/error-handler override exists anywhere in config or src/Shared). An attacker can thus reconstruct field and type names one probe at a time even though introspection appears locked down, defeating the intended controls.
Severity: LOW • Category: A05:2021 Security Misconfiguration (information disclosure) • OWASP: A05:2021; API8:2023
Found by an authorized automated adversarial pentest loop and confirmed by 2 independent skeptic verifiers (unanimous).
Affected code / location
config/packages/api_platform.yaml:8-11,49-53 (graphiql:false, introspection.enabled:false in prod, no error/validation sanitization); config/packages/security.yaml:16 (^/api/graphql POST PUBLIC_ACCESS)
Exploit scenario
{ userr { ide } }. 2. The response validation error returnsDid you mean "user"?/Did you mean "id"?, leaking valid field names. 3. Iterate the guess-and-suggest oracle across types/fields/mutations to rebuild the schema (mutation names, argument fields) without ever calling __schema — enabling targeted attacks against operations that assume schema opacity in prod.Suggested remediation
Register a production GraphQL error/validation handler that suppresses field/type suggestion text (or disables the QuerySuggestion behavior in webonyx) so validation errors return generic messages, consistent with introspection being off.
Verification notes
Related
Part of the enterprise security-hardening effort — umbrella tracker #348; security NFR issues #426 (securability), #441 (vulnerability), #362 (confidentiality), #389 (integrity). Prior security wave: #312–#324. Not a duplicate of the already-fixed items in that range.
Acceptance = the exploit path is closed AND a regression test (unit/Behat/Schemathesis) proves it stays closed.