Personal Access Token scopes aren't honoured by GraphQL subscriptions

Problem

Any personal access token (no matter what scopes are selected) allow you to subscribe to anything in GraphQL regardless of whether or not the scope matches.

This does not grant you access to resources your user cannot access. But it does expand the access of PATs that are meant to be scoped down to less data (eg. read_user).

Specifically from our testing you can create a PAT with scope read_user and subscribe to updates for deleting notes on an issue (eg. workItemNoteDeleted)

How to reproduce

There are some scripts in https://gitlab.com/DylanGriffith/graphql-susbcription-client which is pending being moved to another home. It's private as we don't want to draw attention to this until it is fixed. You can ask for access or you can follow the steps below to set this up:

Here is some node code for subscribing to the API:

// cable.js

import WebSocket from 'ws'
import { createCable } from '@anycable/core'

export default createCable('ws://127.0.0.1:3000/-/cable', {
  websocketImplementation: WebSocket,
  websocketOptions: { headers: { 'Authorization': 'Bearer <YOUR_PAT_HERE>' }}
})
// subscribe.js
import cable from './cable.js'

const subscription = cable.subscribeTo('GraphqlChannel', {
  "query":"subscription workItemNoteDeleted($noteableId: NoteableID) { workItemNoteDeleted(noteableId: $noteableId) {   id   discussionId   lastDiscussionNote   __typename }}","variables":{"noteableId":"gid://gitlab/WorkItem/609"},"operationName":"workItemNoteDeleted","nonce":"619ea684-343a-4060-b458-88cc4ceb028c"
});

//const _ = await subscription.perform('speak', { msg: 'Hello' })

subscription.on('message', msg => {
  console.log(msg);
})
  1. Create these files in a directory
  2. Run yarn init in that directory
  3. Run yarn add @anycable/core
  4. You may need to run yarn add ws
  5. Update your GitLab instance with config.action_cable.disable_request_forgery_protection = true in your config/initializers/action_cable.rb (or figure out how to pass this request forgery check which probably requires some headers)
  6. Update the above code with a URL for your GitLab instance (can be gitlab.com pending solving request forgery check above)
  7. Update the above code with a correct gid://gitlab/WorkItem for a work item on your instance that you have access to read. You can find this by finding the noteable (e.g. Issue) on the rails console, then finding it's GID with issue.to_global_id.to_s, and then replace Issue in the string with WorkItem
  8. Update the above code with <YOUR_PAT_HERE> with a PAT that has read_user scope only
  9. Run the subscription code with node subscribe.js
  10. Create a note on your work item
  11. Delete a note from your work item
  12. You will see log message showing the deleted note

You shouldn't be able to see anything about notes or work items with a read_user scoped token.

Introduced by

In this MR we started allowing people to use PATs with graphql subscriptions. Prior to that only cookies worked so there was no such risk.

!138084 (merged)

Edited by Luke Duncalfe