Fix workflow ID enumeration via distinct not-found vs permission errors

What does this MR do and why?

Before this MR, the Duo Workflows GraphQL resolver returned WORKFLOW_NOT_FOUND for nonexistent IDs but INSUFFICIENT_NAMESPACE_PERMISSIONS or NO_DEFAULT_NAMESPACE for existing
workflows the caller cannot access. The distinct error codes allowed ID enumeration — a permission-style error confirmed the resource existed. This violated the security
guideline
to return 404 semantics on authorization failure to avoid revealing resource existence
(#607062 (closed)).

Error-code unification (workflows_resolver.rb, error_codes.rb): both the not-found and permission-denied paths now return WORKFLOW_NOT_FOUND. The two removed codes
(INSUFFICIENT_NAMESPACE_PERMISSIONS, NO_DEFAULT_NAMESPACE) were introduced by !221090 (merged) as debugging UX; their frontend handlers become dead code and are tracked for removal in
#608162.

Rate-limit keying fix (removes ee/lib/ee/gitlab/application_rate_limiter/labkit_adapter.rb and its prepend_mod hook): unifying the branches required scoping the rate limiter to the requested ID string instead of an AR object (a nonexistent workflow has no object). The EE adapter mapping added by !245893 (merged) constrained the :duo_workflow key slot to AR objects
only, so a string ID was silently dropped, collapsing the throttle from per-user-per-workflow to per-user-global. Removing the mapping lets the string ID fill the slot, restoring
!245893 (merged)'s intended behavior (1 req / 5 min per user per workflow) and extending it to not-found lookups so the throttle cannot be used as a secondary existence oracle.

Spec updates (workflow_audit_events_spec.rb): two expectations asserting INSUFFICIENT_NAMESPACE_PERMISSIONS are updated to WORKFLOW_NOT_FOUND. These were not caught by earlier pipelines because tier-1 predictive runs never selected this file.

References

How to set up and validate locally

  1. Create a Duo workflow in your local instance.
  2. Query for a non-existent workflow ID:
    query {
      duoWorkflowWorkflows(workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/999999") {
        nodes { id }
      }
    }
  3. Confirm the error code is WORKFLOW_NOT_FOUND.
  4. Query for an existing workflow you don't own — confirm the same WORKFLOW_NOT_FOUND error code is returned.
  5. Repeat the query from step 2 (nonexistent workflow ID) within 5 minutes. Confirm the response now contains Too many requests. with no error code — the rate limit applies to not-found lookups.
  6. Immediately query a different nonexistent workflow ID. Confirm it returns WORKFLOW_NOT_FOUND, not Too many requests. — this verifies the rate limit is keyed per workflow ID, not globally per user. (A regression this MR fixes was that the workflow ID was silently dropped from the rate-limit key.)

To reset the rate limit between attempts, either wait 5 minutes or delete the relevant Redis keys:

# GDK (Unix socket)                                                                                                                                                                    
redis-cli -s <gdk-root>/redis/redis.socket --scan --pattern 'labkit:rl*' | xargs redis-cli -s <gdk-root>/redis/redis.socket del                                                        

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Roman Eisner

Merge request reports

Loading
Loading