Enforce composite identity for API-started workflows

What does this MR do and why?

API-triggered workflow starts currently fall back to the organization-level Duo workflow service account when neither a flow mapping nor the caller's composite identity resolves an account. This allows unattended execution to proceed without enforcing the flow's intended composite identity.

This change adds a default-disabled rollout flag that:

  • removes the organization-level service account fallback for API-triggered workflow starts;
  • requires the selected service account to enforce composite identity;
  • preserves explicitly mapped flow accounts over caller composite identities;
  • applies the same validation to AI Catalog consumer execution; and
  • leaves workflow creation without start_workflow unchanged.

The flag remains disabled by default while CEF depends on the legacy fallback.

How to set up and validate locally

Retested on 2026-09-09 with local GDK / GitLab 19.4.0 at commit 393a6759168e6274efce1cc0a5c317ff823180d7.

Setup

  1. Use a project with Duo and remote flows enabled. The test used gitlab-duo/test (project ID 1000000).
  2. Enable enforce_composite_identity_for_api_started_workflows for the project's root namespace.
  3. Configure an AI Catalog flow consumer with a mapped service account. The test used the Developer consumer (ID 13) and service account ID 74.
  4. Use a caller that can read the project but cannot execute the catalog item. The test caller had execute_ai_catalog_item=false.
  5. Send the following request, replacing <caller-token> with that caller's token:
curl --request POST \
  --header "PRIVATE-TOKEN: <caller-token>" \
  --header "Content-Type: application/json" \
  --data '{"project_id":1000000,"ai_catalog_item_consumer_id":13,"start_workflow":true,"goal":"Execute catalog flow"}' \
  "https://gdk.test:3443/api/v4/ai/duo_workflows/workflows"

Manual results

Repeat the same request with the mapped account's composite_identity_enforced value set to each value:

Mapped account setting Expected Actual Result
false Catalog permission error without starting anything HTTP 400: 400 Bad request - ["You have insufficient permissions"] (correlation ID 01M235S5WKHYY6XVZ841XTKV0D) PASS
true The same response, without revealing the setting HTTP 400 with the identical message (correlation ID 01M235WN7KQRD95Y0YD1AA8AJJ) PASS

A Rails-console read before and after both requests showed no side effects:

workflows=63, workflow/workload links=23, pipelines=24
latest workflow ID=83, latest workload ID=35, latest pipeline ID=604

All counts and latest IDs were unchanged. This confirms the authorization ordering leak is fixed at the tested commit.

Focused automated checks

bundle exec rspec ee/spec/requests/api/ai/duo_workflows/workflows_spec.rb \
  --example 'when the user cannot execute the catalog item'
# 2 examples, 0 failures (seed 44157)

bundle exec rspec ee/spec/requests/api/ai/duo_workflows/agent_workflows_spec.rb \
  --example 'when the user cannot execute the catalog item'
# 2 examples, 0 failures (seed 22036)

bundle exec rspec ee/spec/requests/api/ai/duo_workflows/workflows_spec.rb \
  --example 'executes the AI catalog flow with correct service account from project consumer' \
  --example 'when the consumer service account does not enforce composite identity' \
  --example 'allows creating the flow without starting it' \
  --example 'when composite identity does not resolve a service account' \
  --example 'when the composite identity actor is a service account' \
  --example 'when consumer project is nil and enforcement is enabled'
# 11 examples, 0 failures (seed 27034)

The focused checks cover both affected API entry points, mapped-account success, invalid-account rejection, create-only behavior, legacy fallback, composite-identity selection, and the consumer-without-project path.

Screenshots and screencasts

References

Closes #601901

Edited by Alper Akgun

Merge request reports

Loading
Loading