fix: recover introspection block across named fragment spreads; dedup cache hash; fix cache Set counter race

- graphql.go: parseGraphQLQuery now indexes top-level FragmentDefinition nodes
  and checkSelections resolves FragmentSpread by name, closing an
  introspection-blocking bypass (`{ ...F }` where F spreads __schema was
  allowed because the spread carried no selection set and definitions loop
  skipped fragments).
- proxy.go/server.go: handleCaching stores the computed cache hash on the
  request context; performProxyRequest reuses it instead of recomputing a
  full MD5 of the request body per proxied request (fallback preserved for
  direct callers).
- cache/memory: Set serializes the exists-check + counter update + store
  with the embedded mutex so concurrent Sets of the same new key can't
  double-increment entryCount/memoryUsage; regression test added.

Verification: go build, go vet, gofmt clean; full go test ./... and
go test -race ./... green (9 packages); golangci-lint 0 issues; trufflehog clean.
This commit is contained in:
2026-08-13 09:07:52 +01:00
parent 14be2398e8
commit 56eb0af213
6 changed files with 117 additions and 14 deletions
+4
View File
@@ -356,6 +356,10 @@ func handleCaching(c fiber.Ctx, parsedResult *parseGraphQLQueryResult, userID, u
// Calculate query hash for cache key - now includes user context for security
calculatedQueryHash := libpack_cache.CalculateHash(c, userID, userRole)
// Share the precomputed hash with the proxy path (request coalescing) so it
// is not hashed a second time per request (same c, userID, userRole).
c.Locals("query_cache_hash", calculatedQueryHash)
// Set cache time from header or default
if parsedResult.cacheTime == 0 {
if cacheQuery := c.Get("X-Cache-Graphql-Query"); cacheQuery != "" {