Skip to content

fix(metrics): return 400 not 500 for empty or malformed GraphQL request body#3067

Open
arpitjain099 wants to merge 1 commit into
guacsec:mainfrom
arpitjain099:fix/graphql-malformed-body-400-2195
Open

fix(metrics): return 400 not 500 for empty or malformed GraphQL request body#3067
arpitjain099 wants to merge 1 commit into
guacsec:mainfrom
arpitjain099:fix/graphql-malformed-body-400-2195

Conversation

@arpitjain099

Copy link
Copy Markdown

Summary

Fixes #2195.

Posting an empty or malformed JSON body to the /query GraphQL endpoint returned 500 Internal Server Error ("unexpected end of JSON input") instead of 400 Bad Request. As the reporter noted, a 500 is misleading for what is purely a client input problem and sends people hunting for a server-side fault that does not exist.

Root cause

The 500 did not come from the GraphQL server itself. The current gqlgen POST transport already answers a bad request body with 400. The 500 came from the Prometheus metrics middleware MeasureGraphQLResponseDuration in pkg/metrics/prometheus.go, which wraps the GraphQL handler (wired in cmd/guacgql/cmd/server.go). That middleware read the body, ran its own json.Unmarshal to pull out operationName for a metric label, and on any unmarshal error short-circuited the request with http.StatusInternalServerError before the GraphQL handler ever ran. An empty body is not valid JSON, so it tripped this path.

This matches the review feedback on the earlier (now stale-closed) attempt in #2202: a maintainer noted the change there was in the wrong place and that the fix belongs at the GraphQL server side, not bolted onto the metrics path with a second status decision.

Fix

The metrics middleware should observe, not adjudicate. It now best-effort parses the operation name and, regardless of whether the body is valid JSON, forwards the request to the downstream GraphQL handler. The handler then returns the correct status: 400 for an empty or malformed body, 200 for a valid query. The unmarshal error no longer produces a 500. When the body does not parse, the operationName metric label is simply empty, which is the natural outcome for a request that has no parseable operation.

This also removes a redundant body copy that the old code made only to feed that unmarshal.

Behavior before / after

Reproduced with a test that posts to the handler.

Before:

  • empty body: 500 ("unexpected end of JSON input")
  • malformed JSON: 500
  • valid query: 200

After:

  • empty body: 400
  • malformed JSON: 400
  • valid query: 200

Tests

  • pkg/assembler/server/server_badrequest_test.go: end-to-end test that runs the real GraphQL server behind the metrics middleware and asserts 400 for empty and malformed bodies and 200 for a valid query. This is the direct regression guard for [bug] GraphQL server returns 500 when sending empty json payload #2195.
  • pkg/metrics/measure_graphql_test.go: unit test asserting the middleware no longer short-circuits a bad body and forwards the original bytes downstream unchanged.

go build ./..., go vet, and go test ./pkg/metrics/... ./pkg/assembler/server/... pass.

Note on the prior PR

#2202 targeted this same issue but was auto-closed by the stale bot after 90 days of inactivity (it was never rejected on the merits; it had an unrelated merge-conflict artifact and a maintainer question about placement). This PR takes the placement the reviewer pointed to and adds the regression tests that were missing there.

…st body

The Prometheus MeasureGraphQLResponseDuration middleware read the request
body and ran its own json.Unmarshal to extract the operationName metric
label. On any unmarshal error it short-circuited the request with HTTP 500
before the GraphQL handler ran, so an empty or malformed body to /query
returned 500 'unexpected end of JSON input' instead of 400 Bad Request.

Validating the request payload is the GraphQL server's job, and it already
answers a bad body with 400. The metrics middleware now parses the operation
name best-effort and forwards the request downstream regardless of whether
the body is valid JSON, so the handler can return the correct status. A bad
body that has no parseable operation simply gets an empty operationName label.

Adds an end-to-end test (server behind the metrics middleware) asserting 400
for empty and malformed bodies and 200 for a valid query, plus a middleware
unit test that it no longer short-circuits and forwards the body unchanged.

Fixes guacsec#2195

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] GraphQL server returns 500 when sending empty json payload

1 participant