Fix enforced_scans sync with inject_policy

What does this MR do and why?

When a pipeline_execution_policy with pipeline_config_strategy: inject_policy is created with a config YAML which conditionally includes security scans based on whether certain files exist in their project, we analyze this policy configuration to determine which scans will be enforced before knowing which specific project they'll be applied to. When the policy uses variables like $CI_PROJECT_PATH (which means "the project where the pipeline runs"), GitLab can't resolve these variables correctly during analysis because there's no actual pipeline running yet. This results in the extraction of scans from the config to work incorrectly resulting in no scans persisted in enforced_scans in security_policies. This causes the MR to require approval and blocked.

Bug description

When the pipeline_execution_policy is created/updated:

  1. SyncPipelineExecutionPolicyMetadataWorker is called
  2. It calls AnalyzePipelineExecutionPolicyConfigService to analyze the config
  3. The service calls Gitlab::Ci::Config.new(content, project: project, user: current_user) with the config project (not the target project)
  4. The exists rule fails because $CI_COMMIT_SHA cannot be computed as we don't pass the branch (ref) to Gitlab::Ci::Config
  5. The exists rule raises Rules::Rule::Clause::ParseError (see lib/gitlab/ci/build/rules/rule/clause/exists.rb:176-179)
  6. This is caught and re-raised as InvalidIncludeRulesError (see lib/gitlab/ci/config/external/rules.rb:31-32)
  7. The config validation fails, causing AnalyzePipelineExecutionPolicyConfigService to return an error or have an incomplete config
  8. enforced_scans is empty
  9. When MR approval policy checks unblock_rules_using_execution_policies, it can't find the required scans in enforced_scans
  10. Result: MR approval policy incorrectly blocks the merge request

References

Screenshots or screen recordings

Before After
Screenshot_2026-01-15_at_3.05.03_PM Screenshot_2026-01-15_at_4.07.03_PM

How to set up and validate locally

  • In local GDK, enable import from repository URL by going to Admin -> Settings -> General -> Import and export settings
  • Create a group and a project called spp within the group
  • Create an MR with .gitlab/security-policies/policy.yml:
---
pipeline_execution_policy:
- name: Inject
  description: ''
  enabled: true
  pipeline_config_strategy: inject_policy
  content:
    include:
    - project: <GROUP_PATH>/spp
      file: policy-ci.yml
approval_policy:
- name: Dependency scanning and SAST approvals
  description: ''
  enabled: true
  policy_scope:
    projects:
      excluding: []
  rules:
  - type: scan_finding
    scanners:
    - dependency_scanning
    - sast
    vulnerabilities_allowed: 0
    severity_levels: []
    vulnerability_states: []
    branch_type: protected
  actions:
  - type: require_approval
    approvals_required: 1
    group_approvers_ids:
    - 24
  - type: send_bot_message
    enabled: true
  approval_settings:
    block_branch_modification: false
    block_group_branch_modification: false
    prevent_pushing_and_force_pushing: false
    prevent_approval_by_author: false
    prevent_approval_by_commit_author: false
    remove_approvals_with_new_commit: false
    require_password_to_approve: false
  fallback_behavior:
    fail: closed
  policy_tuning:
    unblock_rules_using_execution_policies: true
  • Create policy-ci.yml:
include:
 - template: Jobs/Dependency-Scanning.gitlab-ci.yml
 - local: 'Container-Scanning.latest.gitlab-ci.yml'
   rules:
      - exists:
          paths:
            - Dockerfile
            - "**/Dockerfile"
            - "**/*/Dockerfile"
            - "container/*.dockerfile"
            - '$CS_DOCKERFILE_PATH'
          project: '$CI_PROJECT_PATH'
          ref: '$CI_COMMIT_SHA'

 - template: Jobs/Secret-Detection.latest.gitlab-ci.yml
 - template: Jobs/SAST.gitlab-ci.yml


stages:
  - .pipeline-policy-pre
  - test
  - .post

policy_enforced:
  stage: .pipeline-policy-pre
  script:
    - echo "Hello from inject"

variables:
  SECURE_ENABLE_LOCAL_CONFIGURATION: true
  SECRET_DETECTION_EXCLUDED_PATHS: "node_modules/,bower_components/,vendor/,deps/,venv/,env/,.env/,.venv/,.mypy_cache/,__pycache__/,Pods/,Carthage/,elm-stuff/,.next/,.nuxt/,.angular/,.svelte-kit/,.gradle/,.terraform/,.dart_tool/"  
  FF_TIMESTAMPS: true # enables timestamp in pipeline logs. https://docs.gitlab.com/ee/ci/jobs/job_logs.html#job-log-timestamps
  DOCKER_DIND_VERSION: "24.0.5"

# https://docs.gitlab.com/ee/ci/yaml/workflow.html
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    # prevent multiple pipeline runs for the same branch when MR is open
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never
    # this is to prevent multiple pipeline runs for the same branch when Merge Request is open
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE != "web"
      when: never
    # always run pipeline for the default branch
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: always
    # this runs pipeline after push to branch
    - if: $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "push"
      when: always
  • Link the project (spp) as the security policy project by going to Secure -> Policies -> Edit policy project
  • Import a project (example) within the group
  • Create an MR by updating the README.md and verify that the pipeline is created
  • Verify that the approval is not required

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.

Related to #582104 (closed)

Edited by Andy Schoenen

Merge request reports

Loading