Security gap: failed dependency scanner with allow_failure:true bypasses approval policy

Summary

When a Scan Execution Policy (SEP) or Pipeline Execution Policy (PEP) injects multiple security scanners into a pipeline, and one scanner (e.g. gemnasium-maven-dependency_scanning) fails with exit code 1 producing no report artifact, while other scanners (e.g. secret_detection) succeed and produce reports — the MR Approval Policy with fallback_behavior: fail: closed does not block the merge request. The MR remains "Ready to merge" despite having zero dependency scanning coverage.

Steps to reproduce

  1. Create a group with a compliance framework (e.g. id: 1)
  2. Create a security policy project linked to the group with the following policy.yml:
---
experiments:
  ensure_pipeline_policy_pre_succeeds:
    enabled: true
approval_policy:
- name: mic_vulnerability_approval_policy
  enabled: true
  rules:
  - type: scan_finding
    scanners:
    - dependency_scanning
    vulnerabilities_allowed: 0
    severity_levels:
    - critical
    - high
    vulnerability_states:
    - new_needs_triage
    branch_type: default
    vulnerability_attributes:
      fix_available: true
  - type: scan_finding
    scanners:
    - sast
    - secret_detection
    vulnerabilities_allowed: 0
    severity_levels:
    - critical
    - high
    vulnerability_states:
    - new_needs_triage
    branch_type: default
  actions:
  - type: require_approval
    approvals_required: 1
    role_approvers:
    - maintainer
  approval_settings:
    require_password_to_approve: false
  fallback_behavior:
    fail: closed
  policy_tuning:
    unblock_rules_using_execution_policies: true
scan_execution_policy:
- name: enforce-dep-scan
  enabled: true
  rules:
  - type: pipeline
    branches:
    - "*"
  actions:
  - scan: dependency_scanning
    variables:
      AST_ENABLE_MR_PIPELINES: "true"
  - scan: sast
    variables:
      AST_ENABLE_MR_PIPELINES: "true"
  - scan: secret_detection
    variables:
      AST_ENABLE_MR_PIPELINES: "true"
  policy_scope:
    compliance_frameworks:
    - id: 1
  1. Create a test project with the compliance framework applied, simple .gitlab-ci.yml and a working pom.xml.
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>test-app</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.14.0</version>
    </dependency>
  </dependencies>
</project>

  1. Update pom.xml in a separate branch with a malfunctioned xml which causes gemnasium-maven to fail:
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>test-app</artifactId>
  <version>1.0.0</version>
  <repositories>
    <repository>
      <id>broken-repo</id>
      <url>https://repo.does-not-exist.invalid/maven2</url>
    </repository>
  </repositories>
  <dependencies>
    <dependency>
      <groupId>com.nonexistent</groupId>
      <artifactId>fake-artifact</artifactId>
      <version>9.9.9</version>
    </dependency>
  </dependencies>
</project>
  1. Open a merge request targeting the default branch
  2. Observe the pipeline — gemnasium-maven-dependency_scanning fails with exit code 1, no gl-dependency-scanning-report.json produced; secret_detection succeeds
  3. Observe the MR — it shows "Ready to merge" despite the failed dependency scanner

Example Project

Reproduced on a self-managed GitLab 19.0.1-ee instance. Example project not publicly available

What is the current bug behavior?

  • gemnasium-maven-dependency_scanning fails with exit code 1
  • No gl-dependency-scanning-report.json artifact is produced
  • Pipeline status: passed with warnings (due to allow_failure: true hardcoded in the scanner template)
  • fallback_behavior: fail: closed does not trigger
  • MR approval policy reports: "Security policy violations have been resolved"
  • MR status: Ready to merge — with zero dependency scanning coverage

Pipeline job output:

$ /analyzer run
[INFO] [gemnasium-maven] ▶ GitLab gemnasium-maven analyzer v6.6.1
exit status 1

WARNING: gl-dependency-scanning-report.json: no matching files.
ERROR: No files to upload
ERROR: Job failed: exit code 1

What is the expected correct behavior?

When a scanner configured in a Scan Execution Policy or Pipeline Execution Policy fails and produces no report artifact, fallback_behavior: fail: closed should trigger and block the MR with:

"Pipeline configuration error: Security reports required by policy <policy_name> could not be found."

This correctly occurs when only the failing scanner runs. The bug manifests specifically when multiple scanners run and at least one succeeds — the policy engine evaluates the successful reports and passes, silently ignoring the missing report from the failed scanner.

Relevant logs and/or screenshots

Pipeline evidence (from customer):

{
  "failed_job": {
    "name": "gemnasium-maven-dependency_scanning",
    "status": "failed",
    "allow_failure": true,
    "error": "exit status 1 — no gl-dependency-scanning-report.json produced",
    "artifacts_produced": "NONE"
  },
  "security_policy": {
    "name": "mic_vulnerability_approval_policy",
    "blocks_merge": false,
    "reason": "No vulnerability findings to evaluate (no report uploaded)"
  }
}

Confirmed reproduction matrix:

Configuration Dependency scan Other scans MR blocked?
SEP, single scanner only Failed, no report N/A Yes
SEP, multiple scanners Failed, no report Passed No (bug)
PEP, multiple scanners Failed, no report Passed No (bug)
SEP + PEP combined Failed, no report Passed No (bug)

Toggles tested that do NOT fix the issue:

  • unblock_rules_using_execution_policies: false — still mergeable
  • experiments: ensure_pipeline_policy_pre_exceeds: enabled: true — still mergeable
  • Removing vulnerability_attributes: fix_available: true — still mergeable
  • Splitting dependency_scanning into its own separate rule — still mergeable

Workaround

The following workaround was confirmed to block the MR when the dependency scanner fails:

Requires both of the following:

  1. Enable "All security policy pipelines must succeed" in the project settings (Settings > Merge requests > All security policy pipelines must succeed)

  2. Use a Pipeline Execution Policy (not SEP) and override allow_failure: false on gemnasium-maven-dependency_scanning in the injected CI file:

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

gemnasium-maven-dependency_scanning:
  allow_failure: false

Limitation: This workaround requires switching from SEP to PEP for scan injection, and enabling the project-level setting on all affected projects. It does not fix the underlying bug in fallback_behavior: fail: closed.

Output of checks

  • GitLab version: 19.0.1-ee (self-managed, also reported on GitLab Dedicated)
  • Offering: GitLab Dedicated + Self-Managed
  • Plan: Ultimate

Results of GitLab environment info

Expand for output related to GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
`sudo gitlab-rake gitlab:env:info`)

(For installations from source run and paste the output of:
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)

Results of GitLab application Check

Expand for output related to the GitLab application check

(For installations with omnibus-gitlab package run and paste the output of: sudo gitlab-rake gitlab:check SANITIZE=true)

(For installations from source run and paste the output of: sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)

(we will only investigate if the tests are passing)

Possible fixes

The policy engine's fallback_behavior: fail: closed logic likely evaluates whether any required scan report is present rather than checking that all configured scanners produced reports. When at least one scanner succeeds, the fallback is not triggered even if other configured scanners failed with no report.

The fix should ensure that fallback_behavior: fail: closed triggers independently per scanner rule — if a scanner configured in the policy failed and produced no report, that rule should be treated as a violation regardless of other scanners' outcomes.

Patch release information for backports

If the bug fix needs to be backported in a patch release to a version under the maintenance policy, please follow the steps on the patch release runbook for GitLab engineers.

Refer to the internal "Release Information" dashboard for information about the next patch release, including the targeted versions, expected release date, and current status.

High-severity bug remediation

To remediate high-severity issues requiring an internal release for single-tenant SaaS instances, refer to the internal release process for engineers.

Edited by 🤖 GitLab Bot 🤖