Repository navigation
fix(graphql): enforce configurable query depth limit (#360) - #539
Miracle656 merged 1 commit into
Conversation
|
@lancefair431-code Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Merged. #360 is yours and this is a careful piece of work — it closes the hole rather than appearing to. The thing that makes it right is the part most depth limiters get wrong: fragments are expanded. Mercurius's built-in Four more details I checked and agreed with:
One tradeoff worth flagging, not blocking
If you want the cache back later, there's a route that doesn't weaken anything: measure every operation in the document and take the maximum, rather than only the one Also keep in mind Merge note: Verified on the merge result rather than your branch: Closes #360. |
…key expiry
Three related things, all wanted:
- **Auth failures are now counted** — `admin_auth_failures_total` and
`api_key_auth_failures_total{reason}`, plus
`rate_limit_rejections_total{scope}` on the per-key/IP limiter. A rejected
request was previously invisible, so neither a misconfigured client nor
someone probing `ADMIN_TOKEN` showed up anywhere.
- **A per-instance request ceiling** that sheds with 503 and `Retry-After`
once this process is over `GLOBAL_RATE_LIMIT_MAX` in a rolling minute, with
expensive route templates shed at 80% of it and the probe paths exempt. This
is self-defence rather than a quota, so keeping it per-process and
in-memory is the right call — it protects *this* instance's resources.
- **`api_keys.expires_at`**, with admin key creation accepting an optional
future ISO date-time and `lookupApiKey` refusing an expired key.
`parseGlobalRateLimit` throws on an invalid value instead of falling back to a
default, which is the same instinct as #539's depth limit and correct for the
same reason: a config typo must not quietly mean "no ceiling".
Resolutions and fixes while merging:
- `src/api/usage.ts` re-added a local `isAdminAuthorized` — #517 removed
exactly that copy earlier today. Kept the shared one from `./auth` and the
new metric import.
- `src/app.ts`: kept `allowList: (req) => PROBE_PATHS.has(req.url)` from main
rather than this PR's `req.url === '/status'`, since `/live` and `/ready`
(from #544) are platform-polled and must not be limited either. Took the
PR's metric-emitting `errorResponseBuilder` and the global-ceiling
registration, and kept main's note on the probe-route ceiling.
- `.env.example` was missing `GLOBAL_RATE_LIMIT_MAX`, which the `envDrift`
guard caught. Documented, including that the fleet ceiling is this value
times the instance count by design.
- `npx prisma generate` was needed after the schema change to clear phantom
`expiresAt` errors.
⚠️ Deploy order: `api_keys.expires_at` must exist before this runs, because
`lookupApiKey` selects it — without the column, every authenticated request
fails, not just key creation. Lens has no migrations directory (schema-first
via `db push`), so:
ALTER TABLE "api_keys" ADD COLUMN IF NOT EXISTS "expires_at" TIMESTAMP(3);
Verified on the merge result: `tsc --noEmit` silent, 908 tests pass across 84
suites, nothing red.
Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
Summary
Fixes #360 by enforcing a configurable maximum query depth for the GraphQL endpoint.
Changes Made
.env.exampleif an environment variable is introduced.Testing
npm test.Note: The existing
Load test (prices)failure on the main branch is known. Any test failures should be reported accurately and distinguished from regressions introduced by this change.Scope
This change is limited to GraphQL query-depth validation, its configuration, documentation, and tests. It does not modify pricing logic, financial behavior, or asset and contract identifiers.
Closes: #360