Duo Workflow OAuth token can refresh itself via `direct_access` endpoint.

Summary

The Duo Workflow API exposes a direct_access method which will respond with the necessary configuration to start a workflow executor. For instance the following request:

curl https://gitlab.com/api/v4/ai/duo_workflows/direct_access -H Private-Token:\ $GITLAB_API_PRIVATE_TOKEN -XPOST |jq

Will give us:

{
  "gitlab_rails": {
    "base_url": "https://gitlab.com",
    "token": "[redacted]"
  },
  "duo_workflow_service": {
    "base_url": "cloud.gitlab.com:443",
    "token": "eyJhb.[redacted]",
    "headers": {
      "X-Gitlab-Host-Name": "gitlab.com",
      "X-Gitlab-Instance-Id": "ea8bf810-1d6f-4a6a-b4fd-93e8cbd8b57f",
      "X-Gitlab-Realm": "saas",
      "X-Gitlab-Version": "17.6.0",
      "X-Gitlab-Global-User-Id": "us1PxRHEnhUNfwg/eAJvSXm8pnYdhtAnQroBp2Z1UiA=",
      "X-Gitlab-Duo-Seat-Count": "10000"
    },
    "secure": true
  },
  "duo_workflow_executor": {
    "executor_binary_url": "https://gitlab.com/api/v4/projects/58711783/packages/generic/duo-workflow-executor/v0.0.13/duo-workflow-executor.tar.gz",
    "version": "v0.0.13"
  },
  "workflow_metadata": {
    "extended_logging": true
  }
}

The gitlab_rails.token entry is a OAuth token scoped as ai_workflows to the current user. This token is valid for 2 hours and it can be used within it's lifetime to refresh itself by simply calling the direct_access method with the OAuth token as a Bearer token in the Authorization Header like so:

curl https://gitlab.com/api/v4/ai/duo_workflows/direct_access -H Authorization:\ Bearer\ the_OAuth_token_here; -XPOST | jq

What is the current bug behavior?

The token can refresh itself.

What is the expected correct behavior?

An ai_workflows scoped token should not be able to access the direct_access method on the API.


cc @ottilia_westerlund @jessieay

Edited by Costel Maxim