Repository navigation
fix(security): no role escalation through user management; query cost limits - #18
Merged
Merged
Conversation
… limits - createUser / setUserRoles / setUserPassword / disableUser / enableUser / upsertRole now require the actor to hold every permission of the roles involved (same branch or organisation-wide). A custom role with user:create or user:update could mint an owner, promote itself, or set the owner's password. - costLimit validation rule next to depthLimit: at most 500 fields (aliases and fragment spreads count), 20 top-level fields and 3 document/export fields per operation. 1,000 aliases of trialBalance took 2.2 s; 8 aliased invoice PDFs blocked the event loop for 7 s. The largest client operation has ~120 fields. - With introspection off, 'Did you mean' hints are stripped from validation errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Bj7Tb5PweikTen79CdVH4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Himanshu · project thread
Two findings from a local end-to-end security run (api in production mode on PostgreSQL + PgBouncer + Redis + worker). Both were in the 2026-10-14 audit and are still open on
main.1. Privilege escalation through user management (high once a custom role exists)
Before:
assertRolesonly checked that a role key exists. Reproduced locally with a customuser_managerrole (user:read,user:create,user:update,role:read). That user could:createUserwith roleowner;setUserRoleson itself →owner;setUserPasswordon the owner (account takeover, with no role change at all).After:
AuthServicerefuses (FORBIDDEN) unless the actor holds every permission of the roles involved, in the same branch or organisation-wide (an org-wide grant needs org-wide roles):createUser,setUserRoles);setUserRoles,setUserPassword,disableUser,enableUser), so you cannot manage someone above you;upsertRole).Owners (
*:*) and admins are unaffected for everything they already hold. A newForbiddenErrorinservices/util.tsmaps toFORBIDDEN(an existing code, so the contract doesn't change).2. No query cost limit (DoS)
Before: only
depthLimit(12). Measured locally: 1,000 aliases oftrialBalancetook 2.2 s, and 8 aliasedinvoiceDocumentPDFs in one request took 7 s on the event loop (every other request waits while PDFs render).After: a
costLimitrule next todepthLimit(LIMITSinsrc/server.ts): at most 500 fields per operation (aliases and fragment spreads count), 20 top-level fields and 3 document / export fields. The largest of the client's 141 operations selects ~120 fields with one top-level field, so they all pass (the[static]check validates them). Larger documents still go throughstartDocumentJob.Also, when introspection is off, the
Did you mean "organization"?hints are removed from validation errors (they spelled out the schema).Tests
test/run.ts: user-manager cannot mint an owner, promote itself or set the owner's password, but can grant a role it covers;upsertRoleneedsrole:manage; 1,000 aliases, 21 top-level fields and 4 documents refused; no suggestions with introspection off.npm run typecheckpasses.TEST_BACKENDS=pg npm test: 211 passed, 0 failed (pg, PgBouncer, replicas, worker, client). MongoDB binaries are blocked in this environment, so CI's Mongo run is the first on Mongo. These changes are in the service and validation layers and don't touch storage.schema.graphqlanddocs/04-api-reference.mdare unchanged (no SDL change);llms-full.txtregenerated.Generated by Claude Code