GraphQL resolvers often check the top-level object and skip the nested ones
A common GraphQL setup puts the permission check in the root resolver. project(id: 7) checks that you're a member of project 7 and returns it. Every nested field resolves on its own after that. So a query like project(id: 7) { linkedProjects { name members { email } } } can walk right out of the project you were allowed to see. A linked project might belong to another team, and its members…
Many GraphQL applications place the permission check in the root resolver, such as project(id: 7), which verifies if the user belongs to project 7 and returns the project object. After that, each nested field resolves independently. This means a query like project(id: 7) { linkedProjects { name members { email } } } can bypass the user's authorization for certain objects.
For instance, a linked project might belong to a different team, and its members resolver won't ask if the user can see that team. To address this issue, the authorization check should be placed in each type's resolver or in the data loader that fetches that type, using the object being returned as the key for authorization. Each field that returns another object should be treated as a new authorization question, even if the parent object was allowed.
To test this, sign in as a user who belongs to one project and query relations two or three levels deep. If any object from outside their project scope comes back, it is a security vulnerability. Introspection can provide a full list of these relations to test.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.