doc/user/application_security/policies/merge_request_approval_policies.md
{{< details >}}
{{< /details >}}
{{< history >}}
{{< /history >}}
[!note] Scan result policies feature was renamed to merge request approval policies in GitLab 16.9.
You can use merge request approval policies for multiple purposes, including:
[!note] When a protected branch is created or deleted, the policy approval rules synchronize, with a delay of 1 minute.
The following video gives you an overview of GitLab merge request approval policies (previously scan result policies):
<div class="video-fallback"> See the video: <a href="https://youtu.be/w5I9gcUgr9U">Overview of GitLab Scan Result Policies</a>. </div> <figure class="video-container"> <iframe src="https://www.youtube-nocookie.com/embed/w5I9gcUgr9U" frameborder="0" allowfullscreen> </iframe> </figure>fallback_behavior field.A merge request approval policy is enforced according to the outcome of the pipeline. Consider the following when implementing a merge request approval policy:
When you create a new project, you can enforce both merge request approval policies and security scans on that project. However, incorrectly configured security scanners can affect the merge request approval policies.
There are multiple ways to configure security scans in new projects:
.gitlab-ci.yml configuration file.For simple use cases, you can use the project's CI/CD configuration. For a comprehensive security strategy, consider combining merge request approval policies with the other policy types.
To minimize unnecessary approval requirements and ensure accurate security evaluations:
.gitlab-ci.yml configuration.By implementing your security scans in your new project before you apply a merge request approval policy, you can ensure security scanners run consistently before relying on merge request approval policies, which helps avoid situations where merge requests are blocked due to missing security scans.
To create and verify your security scanners and merge request approval policies together, use this recommended workflow:
.gitlab-ci.yml configuration, a scan execution policy, or a pipeline execution policy.{{< history >}}
multi_pipeline_scan_result_policies. Disabled by default.multi_pipeline_scan_result_policies removed.approval_policy_parent_child_pipeline. Disabled by default.approval_policy_parent_child_pipeline removed.{{< /history >}}
A project can have multiple pipeline types configured. A single commit can initiate multiple pipelines, each of which may contain a security scan.
If a project uses merge request pipelines, you must set the CI/CD variable AST_ENABLE_MR_PIPELINES to "true" for the security scanning jobs to be present in the pipeline.
For more information see Use security scanning tools with merge request pipelines.
For projects where many pipelines have run on the latest commit (for example, dormant projects), policy evaluation considers a maximum of 1,000 pipelines from both the source and target branches of the merge request.
For parent-child pipelines, policy evaluation considers a maximum of 1,000 child pipelines.
[!note] Only project Owners have the permissions to select Security Policy Project.
Once your policy is complete, save it by selecting Configure with a merge request at the bottom of the editor. This redirects you to the merge request on the project's configured security policy project. If a security policy project doesn't link to your project, GitLab creates such a project for you. Existing policies can also be removed from the editor interface by selecting Delete policy at the bottom of the editor.
Most policy changes take effect as soon as the merge request is merged. Any changes that do not go through a merge request and are committed directly to the default branch may require up to 10 minutes before the policy changes take effect.
The policy editor supports YAML mode and rule mode.
[!note] Propagating merge request approval policies created for groups with a large number of projects take a while to complete.
The YAML file with merge request approval policies consists of an array of objects matching the merge request approval
policy schema nested under the approval_policy key. You can configure a maximum of five policies under the approval_policy key.
[!note] Merge request approval policies were defined under the
scan_result_policykey. Until GitLab 17.0, policies can be defined under both keys. Starting from GitLab 17.0, onlyapproval_policykey is supported.
When you save a new policy, GitLab validates its contents against this JSON schema. If you're not familiar with how to read JSON schemas, the following sections and tables provide an alternative.
| Field | Type | Required | Description |
|---|---|---|---|
approval_policy | array of merge request approval policy objects | true | List of merge request approval policies (maximum 5). |
{{< history >}}
enforcement_type field:
security_policy_approval_warn_mode.security_policy_approval_warn_mode removed.security_policy_approval_warn_mode removed.{{< /history >}}
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
name | string | true | Name of the policy. Maximum of 255 characters. | |
description | string | false | Description of the policy. | |
enabled | boolean | true | true, false | Flag to enable (true) or disable (false) the policy. |
rules | array of rules | true | List of rules that the policy applies. | |
actions | array of actions | false | List of actions that the policy enforces. | |
approval_settings | object | false | Project settings that the policy overrides. | |
fallback_behavior | object | false | Settings that affect invalid or unenforceable rules. | |
policy_scope | object of policy_scope | false | Defines the scope of the policy based on the projects, groups, or compliance framework labels you specify. | |
policy_tuning | object | false | (Experimental) Settings that affect policy comparison logic. | |
bypass_settings | object | false | Settings that affect when certain branches, tokens, or accounts can bypass a policy . | |
enforcement_type | string | false | enforce, warn | Defines how the policy is enforced. The default value (if not specified) is enforce, which blocks merge requests when violations are detected. The value warn allows merge requests to proceed but shows warnings and bot comments. |
scan_finding rule type{{< history >}}
vulnerability_attributes:
enforce_vulnerability_attributes_rules.vulnerability_age was added in GitLab 16.2.branch_exceptions field:
security_policies_branch_exceptions.vulnerability_states option newly_detected was removed in GitLab 17.0 and the options new_needs_triage and new_dismissed were added to replace it.{{< /history >}}
This rule enforces the defined actions based on security scan findings.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | scan_finding | The rule's type. |
branches | array of string | true if branch_type field does not exist | [] or the branch's name | Applicable only to protected target branches. An empty array, [], applies the rule to all protected target branches. Cannot be used with the branch_type field. |
branch_type | string | true if branches field does not exist | default or protected | The types of protected branches the given policy applies to. Cannot be used with the branches field. Default branches must also be protected. |
branch_exceptions | array of string | false | Names of branches | Target branches to exclude from this rule. |
scanners | array of string or scanner_with_attributes objects | true | [] or sast, secret_detection, dependency_scanning, container_scanning, dast, coverage_fuzzing, api_fuzzing | The security scanners for this rule to consider. sast includes results from both SAST and SAST IaC scanners. An empty array, [], applies the rule to all security scanners. Specify the scanner either as a string (to apply rule-level settings) or as an object (with per-scanner overrides for severity_levels, vulnerabilities_allowed, and vulnerability_attributes). |
vulnerabilities_allowed | integer | true | Greater than or equal to zero | Number of vulnerabilities allowed before this rule is considered. |
severity_levels | array of string | true | info, unknown, low, medium, high, critical | The severity levels for this rule to consider. |
vulnerability_states | array of string | true | [] or detected, confirmed, resolved, dismissed, new_needs_triage, new_dismissed | All vulnerabilities fall into two categories: |
Newly Detected Vulnerabilities - Vulnerabilities identified in the merge request branch itself but that do not currently exist on the MR's target branch. This policy option requires a pipeline to complete before the rule is evaluated so that it knows whether vulnerabilities are newly detected or not. Merge requests are blocked until the pipeline and necessary security scans are complete. The new_needs_triage option considers the status
• Detected
The new_dismissed option considers the status
• Dismissed
Pre-Existing Vulnerabilities - these policy options are evaluated immediately and do not require a pipeline complete as they consider only vulnerabilities previously detected in the default branch.
• Detected - the policy looks for vulnerabilities in the detected state.
• Confirmed - the policy looks for vulnerabilities in the confirmed state.
• Dismissed - the policy looks for vulnerabilities in the dismissed state.
• Resolved - the policy looks for vulnerabilities in the resolved state.
An empty array, [], covers the same statuses as ['new_needs_triage', 'new_dismissed']. |
| vulnerability_attributes | object | false | vulnerability_attributes object | All vulnerability findings are considered by default. Apply these filters to consider only vulnerability findings that match specific criteria. See vulnerability_attributes object for details. |
| vulnerability_age | object | false | N/A | Filter pre-existing vulnerability findings by age. A vulnerability's age is calculated as the time since it was detected in the project. The criteria are operator, value, and interval.
operator criterion specifies if the age comparison used is older than (greater_than) or younger than (less_than).value criterion specifies the numeric value representing the vulnerability's age.interval criterion specifies the unit of measure of the vulnerability's age: day, week, month, or year.Example: operator: greater_than, value: 30, interval: day. |
vulnerability_attributes object{{< history >}}
known_exploited, epss_score, and enrichment_data_unavailable fields introduced in GitLab 18.11 with a feature flag named security_policies_kev_filter. Disabled by default.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
false_positive | boolean | false | true, false | Filter by false positive status. true includes only false positives; false excludes them. |
fix_available | boolean | false | true, false | Filter by fix availability. true includes only vulnerabilities with a fix available; false includes only those without. |
known_exploited | boolean | false | true, false | Filter based on the CISA Known Exploited Vulnerabilities (KEV) catalog. When true, includes only vulnerabilities that are actively exploited in the wild. When false, does not filter vulnerabilities based on known exploit status. |
epss_score | object | false | {operator, value} object | Filter based on the Exploit Prediction Scoring System (EPSS) score. EPSS estimates the probability (0 to 1) that a vulnerability will be exploited. As an object: operator can be greater_than, or less_than; value is a number between 0.0 and 1.0. Example: {operator: greater_than, value: 0.8}. |
enrichment_data_unavailable | object | false | {action: "block"} or {action: "ignore"} | Define how to handle CVE vulnerabilities with unavailable enrichment data (missing EPSS score or known exploit status). When 'block', vulnerabilities without enrichment data are evaluated according to the rule-level criteria. When 'ignore', vulnerabilities without enrichment data are excluded from policy evaluation. |
scanner_with_attributes object{{< history >}}
atomic_scanner_rule_criteria. Enabled by default. Generally available in GitLab 18.11. Feature flag removed.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
When a scanner is specified as an object instead of a string, each scanner type is evaluated independently with its own criteria. Any field not specified at the scanner level falls back to the setting defined by the rule-level value.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | sast, secret_detection, dependency_scanning, container_scanning, dast, coverage_fuzzing, api_fuzzing | The scanner type. |
severity_levels | array of string | false | info, unknown, low, medium, high, critical | Overrides the rule-level severity_levels for this scanner. |
vulnerabilities_allowed | integer | false | Greater than or equal to zero | Overrides the rule-level vulnerabilities_allowed for this scanner. |
vulnerability_attributes | object | false | vulnerability_attributes object | Overrides the rule-level vulnerability_attributes for this scanner. |
Example using per-scanner criteria:
rules:
- type: scan_finding
branches: []
scanners:
- type: dependency_scanning
vulnerability_attributes:
fix_available: true
vulnerabilities_allowed: 0
severity_levels:
- critical
- high
- type: container_scanning
vulnerability_attributes:
known_exploited: true
epss_score:
value: 0.5
operator: greater_than
enrichment_data_unavailable:
action: block
vulnerabilities_allowed: 0
severity_levels:
- critical
vulnerabilities_allowed: 5
severity_levels:
- critical
- high
- medium
vulnerability_states:
- new_needs_triage
In this example:
vulnerabilities_allowed: 5 and severity_levels serve as defaults for any scanner without explicit overrides.license_finding rule type{{< history >}}
license_scanning_policies.license_scanning_policies removed.branch_exceptions field was introduced in GitLab 16.3 with a flag named security_policies_branch_exceptions. Enabled by default. Generally available in GitLab 16.5. Feature flag removed.licenses field was introduced in GitLab 17.11 with a flag named exclude_license_packages. Feature flag removed.{{< /history >}}
This rule enforces the defined actions based on license findings.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | license_finding | The rule's type. |
branches | array of string | true if branch_type field does not exist | [] or the branch's name | Applicable only to protected target branches. An empty array, [], applies the rule to all protected target branches. Cannot be used with the branch_type field. |
branch_type | string | true if branches field does not exist | default or protected | The types of protected branches the given policy applies to. Cannot be used with the branches field. Default branches must also be protected. |
branch_exceptions | array of string | false | Names of branches | Target branches to exclude from this rule. |
match_on_inclusion_license | boolean | true if licenses field does not exists | true, false | Whether the rule matches inclusion or exclusion of licenses listed in license_types. |
license_types | array of string | true if licenses field does not exists | license types | SPDX license names to match on, for example Affero General Public License v1.0 or MIT License. |
license_states | array of string | true | newly_detected, detected | Whether to match newly detected and/or previously detected licenses. The newly_detected state triggers approval when either a new package is introduced or when a new license for an existing package is detected. |
licenses | object | true if license_types field does not exists | licenses object | SPDX license names to match on including package exceptions. |
licenses object| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
denied | object | true if allowed field does not exist | array of licenses_with_package_exclusion objects | The list of denied licenses including package exceptions. |
allowed | object | true if denied field does not exist | array of licenses_with_package_exclusion objects | The list of allowed licenses including package exceptions. |
licenses_with_package_exclusion objectUse the licenses_with_package_exclusion object to define a license name and optional
package exclusions.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
name | string | true | SPDX license name | SPDX license name. |
packages | object | false | packages object | List of packages exceptions for the given license. |
[!note] The
namefield must be a valid SPDX license name. The valueunknownis not a recognized SPDX license name, and is not supported in thelicensesfield. Package-level exclusions configured forunknownlicenses are ignored during merge request approval evaluation. To manage packages withunknownlicenses, use thelicense_typesfield, or allowunknownas a license in your policy. For more information, see license approval policies block merge requests due tounknownlicenses.
packages objectUse the packages object to define package URL exclusions for a license entry.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
excluding | object | true | {purls: array of strings using the uri format} | List of package exceptions for the given license. Define the list of packages exceptions using the purl components scheme:type/name@version. The scheme:type/name components are required. The @ and version are optional. If a version is specified, only that version is considered an exception. If no version is specified and the @ character is added at the end of the purl, only packages with the exact name is considered a match. If the @ character is not added to the package name, all packages with the same prefix for the given license are matches. For example, a purl pkg:gem/bundler matches the bundler and bundler-stats packages because both packages use the same license. Defining a purl pkg:gem/bundler@ matches only the bundler package. |
any_merge_request rule type{{< history >}}
branch_exceptions field was introduced in GitLab 16.3 with a flag named security_policies_branch_exceptions. Enabled by default. Generally available in GitLab 16.5. Feature flag removed.any_merge_request rule type was introduced in GitLab 16.4. Enabled by default. Generally available in GitLab 16.6. Feature flag removed.{{< /history >}}
This rule enforces the defined actions for any merge request based on the commits signature.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | any_merge_request | The rule's type. |
branches | array of string | true if branch_type field does not exist | [] or the branch's name | Applicable only to protected target branches. An empty array, [], applies the rule to all protected target branches. Cannot be used with the branch_type field. |
branch_type | string | true if branches field does not exist | default or protected | The types of protected branches the given policy applies to. Cannot be used with the branches field. Default branches must also be protected. |
branch_exceptions | array of string | false | Names of branches | Target branches to exclude from this rule. |
commits | string | true | any, unsigned | Whether the rule matches for any commits, or only if unsigned commits are detected in the merge request. |
require_approval action type{{< history >}}
require_approval actions:
multiple_approval_actions.multiple_approval_actions removed.role_approvers:
security_policy_custom_roles. Enabled by default.security_policy_custom_roles removed.{{< /history >}}
This action makes an approval rule required when the conditions are met for at least one rule in the defined policy.
If you specify multiple approvers in the same require_approval block, any of the eligible approvers can satisfy the approval requirement. For example, if you specify two group_approvers and approvals_required as 2, both of the approvals can come from the same group. To require multiple approvals from unique approver types, use multiple require_approval actions.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | require_approval | The action's type. |
approvals_required | integer | true | Greater than or equal to zero | The number of MR approvals required. |
user_approvers | array of string | Conditional | Username of one of more users | The users to consider as approvers. Users must have access to the project to be eligible to approve. |
user_approvers_ids | array of integer | Conditional <sup>1</sup> | ID of one of more users | The IDs of users to consider as approvers. Users must have access to the project to be eligible to approve. |
group_approvers | array of string | Conditional <sup>1</sup> | Path of one of more groups | The groups to consider as approvers. Users with direct membership in the group are eligible to approve. |
group_approvers_ids | array of integer | Conditional <sup>1</sup> | ID of one of more groups | The IDs of groups to consider as approvers. Users with direct membership in the group are eligible to approve. |
role_approvers | array of string | Conditional <sup>1</sup> | One or more roles (for example: owner, maintainer). You can also specify custom roles (or custom role identifiers in YAML mode) as role_approvers if the custom roles have the permission to approve merge requests. The custom roles can be selected along with user and group approvers. | The roles that are eligible to approve. Only users with the exact role you specify can approve. Roles with higher privileges are not automatically included. For example, if you select developer, only users with the Developer role can approve. Maintainers and Owners cannot approve unless you also add them. |
Footnotes:
user_approvers, user_approvers_ids, group_approvers, group_approvers_ids, or role_approvers).Valid user_approvers:
actions:
- type: require_approval
approvals_required: 2
user_approvers:
- alice
- bob
Valid group_approvers:
actions:
- type: require_approval
approvals_required: 1
group_approvers:
- security-team
Valid role_approvers:
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- maintainer
Valid with multiple approver types:
actions:
- type: require_approval
approvals_required: 2
user_approvers:
- alice
group_approvers:
- security-team
role_approvers:
- maintainer
Invalid because no approvers specified:
actions:
- type: require_approval
approvals_required: 2
# ERROR: At least one approver field must be specified
# This configuration will fail validation
send_bot_message action type{{< history >}}
send_bot_message action type for projects:
approval_policy_disable_bot_comment. Disabled by default.approval_policy_disable_bot_comment removed.send_bot_message action type for groups:
approval_policy_disable_bot_comment_group. Disabled by default.approval_policy_disable_bot_comment_group removed.{{< /history >}}
This action enables configuration of the bot message in merge requests when policy violations are detected.
If the action is not specified, the bot message is enabled by default. If there are multiple policies defined,
the bot message is sent as long as at least one of those policies has the send_bot_message action is enabled.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
type | string | true | send_bot_message | The action's type. |
enabled | boolean | true | true, false | Whether a bot message should be created when policy violations are detected. Default: true |
{{< history >}}
security_policy_approval_warn_mode. Disabled by defaultsecurity_policy_warn_mode_license_scanning. Enabled by default.security_policy_approval_warn_mode removed.{{< /history >}}
[!flag] The availability of this feature is controlled by a feature flag. For more information, see the history.
Warn mode allows security teams to test and validate the impact of security policies before applying full enforcement, reducing developer friction when applying new security policies. When a policy is configured with enforcement_type: warn, the merge request provides an option to bypass any merge request approval policy violations.
When warn mode is enabled (enforcement_type: warn) and a merge request triggers a security policy violation, the policy enforcement is different in several ways:
To enable warn mode for a merge request approval policy, set the enforcement_type field to warn:
approval_policy:
- name: Warn mode policy
description: ''
enabled: true
enforcement_type: warn
policy_scope:
projects:
excluding: []
rules:
- type: scan_finding
scanners:
- secret_detection
vulnerabilities_allowed: 0
severity_levels: []
vulnerability_states: []
branch_type: protected
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- developer
- maintainer
- type: send_bot_message
enabled: true
approval_settings{{< history >}}
block_group_branch_modification field:
scan_result_policy_block_group_branch_modification.scan_result_policy_block_group_branch_modification removed.block_unprotecting_branches field
scan_result_policy_settings. Disabled by default.block_unprotecting_branches field was replaced by block_branch_modification field in GitLab 16.7.scan_result_policies_block_unprotecting_branches feature flag replaced the scan_result_policy_settings feature flag in 16.4.
scan_result_policies_block_unprotecting_branches removed.prevent_approval_by_author, prevent_approval_by_commit_author, remove_approvals_with_new_commit, and require_password_to_approve fields:
scan_result_any_merge_request. Disabled by default.scan_result_any_merge_request removed.prevent_pushing_and_force_pushing field
scan_result_policies_block_force_push. Disabled by default.scan_result_policies_block_force_push removed.{{< /history >}}
The settings set in the policy overwrite settings in the project.
| Field | Type | Required | Possible values | Applicable rule type | Description |
|---|---|---|---|---|---|
block_branch_modification | boolean | false | true, false | All | When enabled, prevents a user from removing a branch from the protected branches list, deleting a protected branch, or changing the default branch if that branch is included in the security policy. This ensures users cannot remove protection status from a branch to merge vulnerable code. Enforced based on branches, branch_type and policy_scope and regardless of detected vulnerabilities. |
block_group_branch_modification | boolean or object | false | true, false, { enabled: boolean, exceptions: [{ id: Integer}] } | All | When enabled, prevents a user from removing group-level protected branches on every group the policy applies to. If block_branch_modification is true, implicitly defaults to true. Add top-level groups that support group-level protected branches as exceptions |
prevent_approval_by_author | boolean | false | true, false | Any merge request | When enabled, merge request authors cannot approve their own MRs. This ensures code authors cannot introduce vulnerabilities and approve code to merge. |
prevent_approval_by_commit_author | boolean | false | true, false | Any merge request | When enabled, users who have contributed code to the MR are ineligible for approval. This ensures code committers cannot introduce vulnerabilities and approve code to merge. |
remove_approvals_with_new_commit | boolean | false | true, false | Any merge request | When enabled, if an MR receives all necessary approvals to merge, but then a new commit is added, new approvals are required. This ensures new commits that may include vulnerabilities cannot be introduced. |
require_password_to_approve | boolean | false | true, false | Any merge request | When enabled, approvers must authenticate again before approving. The approver can re-authenticate using their password or SAML, depending on their configured authentication method. This adds an extra layer of security to ensure the approver's identity. For more information, see require user re-authentication to approve. |
prevent_pushing_and_force_pushing | boolean | false | true, false | All | When enabled, prevents users from pushing and force pushing to a protected branch if that branch is included in the security policy. This ensures users do not bypass the merge request process to add vulnerable code to a branch. |
These settings are enforced only on merge requests that have violations against the policy:
prevent_approval_by_authorprevent_approval_by_commit_authorremove_approvals_with_new_commitrequire_password_to_approveIf a merge request has no policy violations, the settings have no effect on that merge request.
These settings are always enforced if the policy is active, regardless of whether a merge request has policy violations:
block_branch_modificationblock_group_branch_modificationprevent_pushing_and_force_pushing settingsfallback_behavior{{< history >}}
fallback_behavior field:
security_scan_result_policies_unblock_fail_open_approval_rules. Disabled by default.security_scan_result_policies_unblock_fail_open_approval_rules removed.{{< /history >}}
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
fail | string | false | open or closed | closed (default): Invalid or unenforceable rules of a policy require approval. open: Invalid or unenforceable rules of a policy do not require approval. |
policy_tuningunblock_rules_using_execution_policies{{< history >}}
unblock_rules_using_pipeline_execution_policies. Enabled by default.unblock_rules_using_pipeline_execution_policies removed.{{< /history >}}
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
unblock_rules_using_execution_policies | boolean | false | true, false | When enabled, approval rules do not block merge requests when a scan is required by a scan execution policy or a pipeline execution policy but a required scan artifact is missing from the source branch. This option only works when the project or group has an existing scan execution policy or pipeline execution policy with matching scanners. |
You can only exclude license finding rules if they target newly detected states only (license_states is set to newly_detected).
policy_tuning with a scan execution policyYou can use this example in a .gitlab/security-policies/policy.yml file stored in a
security policy project:
scan_execution_policy:
- name: Enforce dependency scanning
description: ''
enabled: true
policy_scope:
projects:
excluding: []
rules:
- type: pipeline
branch_type: all
actions:
- scan: dependency_scanning
approval_policy:
- name: Dependency scanning approvals
description: ''
enabled: true
policy_scope:
projects:
excluding: []
rules:
- type: scan_finding
scanners:
- dependency_scanning
vulnerabilities_allowed: 0
severity_levels: []
vulnerability_states: []
branch_type: protected
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- developer
- type: send_bot_message
enabled: true
fallback_behavior:
fail: closed
policy_tuning:
unblock_rules_using_execution_policies: true
policy_tuning with a pipeline execution policy[!warning] This feature does not work with pipeline execution policies created before GitLab 17.10. To use this feature with older pipeline execution policies, copy, delete, and recreate the policies. For more information, see Recreate pipeline execution policies created before GitLab 17.10.
You can use this example in a .gitlab/security-policies/policy.yml file stored in a
security policy project:
---
pipeline_execution_policy:
- name: Enforce dependency scanning
description: ''
enabled: true
pipeline_config_strategy: inject_policy
content:
include:
- project: my-group/pipeline-execution-ci-project
file: policy-ci.yml
ref: main # optional
The linked pipeline execution policy CI/CD configuration in policy-ci.yml:
include:
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
Pipeline execution policies created before GitLab 17.10 do not contain the data required
to use the policy_tuning feature. To use this feature with older pipeline execution policies,
copy and delete the old policies, then recreate them.
<i class="fa-youtube-play" aria-hidden="true"></i>
For a video walkthrough, see Security policies: Recreate a pipeline execution policy for use with policy_tuning.
To recreate a pipeline execution policy:
<!-- markdownlint-disable MD044 -->security_report_time_window{{< history >}}
approval_policy_time_window.approval_policy_time_window removed.{{< /history >}}
In busy projects, the most recent pipeline may not have completed security scans available right away, which blocks security report comparisons. Use the security_report_time_window setting to security reports from recently completed pipelines instead. The security reports cannot be older than the time window, specified in minutes prior to the creation of the target branch pipeline. This setting does not apply if the selected pipeline already has completed security reports.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
security_report_time_window | integer | false | 1 to 10080 (7 days) | Specifies the time window in minutes for choosing the target pipeline for the security report comparison. |
To customize policy enforcement, you can define a policy's scope to either include or exclude specified projects, groups, or compliance framework labels. For more details, see Scope.
[!note] Setting a
policy_scopefield to an empty collection (for example,including: []) is treated the same as omitting the field, so the policy applies to all projects for that scope dimension. To disable a policy entirely, useenabled: false. For more details, see Empty collections inpolicy_scope.
bypass_settingsThe bypass_settings field allows you to specify exceptions to the policy for certain branches, access tokens, or service accounts. When a bypass condition is met, the policy is not enforced for the matching merge request or branch.
| Field | Type | Required | Description |
|---|---|---|---|
branches | array | false | List of source and target branches (by name or pattern) that bypass the policy. |
access_tokens | array | false | List of access token IDs that bypass the policy. |
service_accounts | array | false | List of service account IDs that bypass the policy. |
users | array | false | List of user IDs that can bypass the policy. |
groups | array | false | List of group IDs that can bypass the policy. |
roles | array | false | List of default roles that can bypass the policy. |
custom_roles | array | false | List of custom role IDs that can bypass the policy. |
{{< history >}}
approval_policy_branch_exceptions. Enabled by defaultapproval_policy_branch_exceptions removed.{{< /history >}}
With branch-based exceptions, you can configure merge request approval policies to automatically waive approval requirements for specific source and target branch combinations. This enables you to preserve security governance and maintain strict approval rules for certain types of merges, such as feature-to-main, while allowing more flexibility for others, such as release-to-main. Bypass events are logged as audit events in a security policy project.
| Field | Type | Required | Possible values | Description |
|---|---|---|---|---|
source | object | false | name (string) or pattern (string) | Source branch exception. Specify either an exact name or a pattern. |
target | object | false | name (string) or pattern (string) | Target branch exception. Specify either an exact name or a pattern. |
{{< history >}}
security_policies_bypass_options_tokens_accounts. Enabled by defaultsecurity_policies_bypass_options_tokens_accounts removed.{{< /history >}}
With access token and service account exceptions, you can designate specific service accounts and access tokens that can bypass branch protections enforced by merge request approval policies when necessary. This approach enables automations that you trust to operate without manual approval while maintaining restrictions for human users. For example, trusted automations might include CI/CD pipelines, repository mirroring, and automated updates. Bypass events are logged as audit events in a security policy project.
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | true | The ID of the access token or service account. |
{{< history >}}
security_policies_bypass_options_group_roles. Enabled by default.security_policies_bypass_options_group_roles removed.{{< /history >}}
You can prepare for urgent situations by designating specific users, groups, roles, or custom roles that can bypass merge request approval policies. This capability provides flexibility for emergency responses and maintaining governance controls. To allow a user, group, role, or custom role the ability to bypass security policies, you grant them an exception. Bypass events are logged as audit events in a security policy project.
Users who have these exceptions can bypass at two levels:
security_policy.bypass_reason Git push options[!note] The
security_policy.bypass_reasonpush option works only for branches with push protection from a merge request approval policy configured withapproval_settings. Pushes to protected branches that are not covered by a merge request approval policy cannot be bypassed with this option.
bypass_settings:
access_tokens:
- id: 123
- id: 456
service_accounts:
- id: 789
- id: 1011
users:
- id: 123
- id: 456
groups:
- id: 789
- id: 1011
roles:
- maintainer
- developer
custom_roles:
- id: 789
- id: 1011
policy.yml in a security policy projectYou can use this example in a .gitlab/security-policies/policy.yml file stored in a
security policy project:
---
approval_policy:
- name: critical vulnerability CS approvals
description: critical severity level only for container scanning
enabled: true
rules:
- type: scan_finding
branches:
- main
scanners:
- container_scanning
vulnerabilities_allowed: 0
severity_levels:
- critical
vulnerability_states: []
vulnerability_attributes:
false_positive: true
fix_available: true
actions:
- type: require_approval
approvals_required: 1
user_approvers:
- adalberto.dare
- name: secondary CS approvals
description: secondary only for container scanning
enabled: true
rules:
- type: scan_finding
branches:
- main
scanners:
- container_scanning
vulnerabilities_allowed: 1
severity_levels:
- low
- unknown
vulnerability_states:
- detected
vulnerability_age:
operator: greater_than
value: 30
interval: day
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- owner
- 1002816 # Example custom role identifier called "AppSec Engineer"
- name: critical vulnerability CS approvals
description: high/critical severity level only for SAST scanning
enabled: true
enforcement_type: warn
rules:
- type: scan_finding
branch_type: default
scanners:
- sast
vulnerabilities_allowed: 0
severity_levels:
- critical
- high
vulnerability_states: []
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- maintainer
In this example:
critical vulnerabilities identified by container scanning requires
one approval from alberto.dare.low or unknown vulnerability older than 30 days identified by
container scanning requires one approval from either a project member with the Owner role or a user with the custom role AppSec Engineer.critical or high severity vulnerabilities, identified by SAST scanning, triggers the warn mode policy. Warn mode generates a bot comment and blocks the merge request. A developer can then bypass the policy violation. Optionally, a maintainer can also approve the merge request.You can use this example in the YAML mode of the Merge Request Approval Policy editor. It corresponds to a single object from the previous example:
type: approval_policy
name: critical vulnerability CS approvals
description: critical severity level only for container scanning
enabled: true
rules:
- type: scan_finding
branches:
- main
scanners:
- container_scanning
vulnerabilities_allowed: 1
severity_levels:
- critical
vulnerability_states: []
actions:
- type: require_approval
approvals_required: 1
user_approvers:
- adalberto.dare
{{< history >}}
scan_finding was changed in GitLab 16.8 with a flag named scan_result_policy_merge_base_pipeline. Disabled by default.scan_result_policy_merge_base_pipeline removed.{{< /history >}}
feature/main). This ensures the most comprehensive evaluation of scan results.new_needs_triage & new_dismissed), the comparison is performed against all the supported pipeline sources in the common ancestor between the source and the target branch. An exception is when using Merged Results pipelines, in which case the comparison is done against the tip of the MR's target branch.detected, confirmed, resolved, dismissed), the comparison is always done against the tip of the default branch (for example, main).CI_PIPELINE_SOURCE variable) when comparing results from both the source and target branches when determining if a merge request requires approval. Pipelines with source webide are not supported.For merge request approval policies that are scoped to newly detected findings (new_needs_triage or new_dismissed statuses), it's important to understand the implications of this vulnerability state. A finding is considered newly detected if it exists on the merge request's branch but not on the target branch. When a merge request with a branch that contains newly detected findings is approved and merged, approvers are "accepting the risk" of those vulnerabilities. If one or more of the same vulnerabilities is detected after this time, the status would be detected and thus ignored by a policy configured to consider new_needs_triage or new_dismissed findings. For example:
When using new_needs_triage and new_dismissed vulnerability states, the policy will block MRs for any findings matching policy rules if they are new and not yet triaged, even if they have been dismissed. If you want to ignore vulnerabilities newly detected and then dismissed within the merge request, you may use only the new_needs_triage status.
When using license approval policies, the combination of project, component (dependency), and license are considered in the evaluation. If a license is approved as an exception, future merge requests don't require approval for the same combination of project, component (dependency), and license. The component's version is not be considered in this case. If a previously approved package is updated to a new version, approvers will not need to re-approve. For example:
AGPL-1.0. A change is made in project demo for component osframework that violates the policy. If approved and merged, future merge requests to osframework in project demo with the license AGPL-1.0 don't require approval.Merge request approval policies require an additional approval step in some situations. For example:
The number of security jobs is reduced in the working branch and no longer matches the number of security jobs in the target branch. Users can't skip the Scanning Result Policies by removing scanning jobs from the CI/CD configuration. Only the security scans that are configured in the merge request approval policy rules are checked for removal.
For example, consider a situation where the default branch pipeline has four security scans:
sast, secret_detection, container_scanning, and dependency_scanning. A merge request approval
policy enforces two scanners: container_scanning and dependency_scanning. If an MR removes a
scan that is configured in merge request approval policy, container_scanning for example, an additional
approval is required.
Someone stops a pipeline security job, and users can't skip the security scan.
A job in a merge request fails and is configured with allow_failure: false. As a result, the pipeline is in a blocked state.
A pipeline has a manual job that must run successfully for the entire pipeline to pass.
Merge request approval policies evaluate the artifact reports generated by scanners in your pipelines after the pipeline has completed. Merge request approval policies focus on evaluating the results and determining approvals based on the scan result findings to identify potential risks, block merge requests, and require approval.
Merge request approval policies do not extend beyond that scope to reach into artifact files or scanners. Instead, GitLab trusts the results from artifact reports. This gives teams flexibility in managing their scan execution and supply chain, and customizing scan results generated in artifact reports (for example, to filter out false positives) if needed.
Lock file tampering, for example, is outside of the scope of security policy management, but may be mitigated through use of Code owners or external status checks. For more information, see issue 433029.
To avoid unnecessary approval requirements, these additional filters help ensure you only block MRs on the most actionable findings.
By setting fix_available to false in YAML, or is not and Fix Available in the policy editor, the finding is not considered a policy violation when the finding has a solution or remediation available. Solutions appear at the bottom of the vulnerability object under the heading Solution. Remediations appear as a Resolve with Merge Request button within the vulnerability object.
The Resolve with Merge Request button only appears when one of the following criteria is met:
GIT_STRATEGY: fetch has been set. Additionally, the vulnerability must have a package containing a fix that is available for the repositories enabled for the container image.Fix Available only applies to dependency scanning and container scanning.
By using the False Positive attribute, similarly, you can ignore findings detected by a policy by setting false_positive to false (or set attribute to Is not and False Positive in the policy editor).
The False Positive attribute only applies to findings detected by the Vulnerability Extraction Tool for SAST results.
When a user changes the status of a vulnerability (for example, dismisses the vulnerability in the vulnerability details page), GitLab does not automatically reevaluate merge request approval policies due to performance reasons. To retrieve updated data from vulnerability reports, update your merge request or rerun the related pipelines.
This behavior ensures optimal system performance and maintains security policy enforcement. The policy evaluation occurs during the next pipeline run or when the merge request is updated, but not immediately when the vulnerability state changes.
To reflect vulnerability state changes in the policies immediately manually run the pipeline or push a new commit to the merge request.
You may notice inconsistencies between what the merge request security widget displays and what the security bot comments indicate regarding vulnerabilities. These features use different data sources and comparison methods for security findings, which can result in differences in what they display.
Data sources:
The difference in data sources can lead to inconsistent behavior in several scenarios.
When the latest pipeline on your target branch fails to run security scans properly (for example, due to a misconfiguration or job failures), the security bot might report new findings and require approval as a precautionary measure because it cannot compare results effectively. Meanwhile, the security widget might show no new vulnerabilities because it uses previously stored vulnerability data.
If there are multiple commits on the target branch that change the security profile between when the widget makes its comparison and when the bot makes its comparison, results can differ.
To minimize confusion when using these security features:
{{< details >}}
{{< /details >}}
On GitLab Self-Managed from 15.0 to 16.4, the most likely cause is that the project was exported from a group and imported into another, and had merge request approval policy rules. These rules are stored in a separate project to the one that was exported. As a result, the project contains policy rules that reference entities that don't exist in the imported project's group. The result is policy rules that are invalid, duplicated, or both.
To remove all invalid merge request approval policy rules from a GitLab instance, an administrator can run the following script in the Rails console.
Project.joins(:approval_rules).where(approval_rules: { report_type: %i[scan_finding license_scanning] }).where.not(approval_rules: { security_orchestration_policy_configuration_id: nil }).find_in_batches.flat_map do |batch|
batch.map do |project|
# Get projects and their configuration_ids for applicable project rules
[project, project.approval_rules.where(report_type: %i[scan_finding license_scanning]).pluck(:security_orchestration_policy_configuration_id).uniq]
end.uniq.map do |project, configuration_ids| # Take only unique combinations of project + configuration_ids
# If you find more configurations than what is available for the project, take records with the extra configurations
[project, configuration_ids - project.all_security_orchestration_policy_configurations.pluck(:id)]
end.select { |_project, configuration_ids| configuration_ids.any? }
end.each do |project, configuration_ids|
# For each found pair project + ghost configuration, remove these rules for a given project
Security::OrchestrationPolicyConfiguration.where(id: configuration_ids).each do |configuration|
configuration.delete_scan_finding_rules_for_project(project.id)
end
# Ensure you sync any potential rules from new group's policy
Security::ScanResultPolicies::SyncProjectWorker.perform_async(project.id)
end
When using new_needs_triage and new_dismissed, some findings may require approval when they are not introduced by the merge request (such as a new CVE on a related dependency). These findings will not be present within the MR widget, but will be highlighted in the policy bot comment and pipeline report.
policy.yml was manually invalidatedIn GitLab 17.2 and earlier, you may find that policies defined in a policy.yml file are enforced,
even though the file was manually edited and no longer validates
against the policy schema. This issue occurs because of
a bug in the policy synchronization logic.
Potential symptoms include:
approval_settings still block the removal of branch protections, block force-pushes or otherwise affect open merge requests.any_merge_request policies still apply to open merge requests.To resolve this you can:
policy.yml file that defines the policy so that it becomes valid again.policy.yml file is stored.When using merge request approval policies, you may encounter situations where merge requests are blocked, including in new projects or when certain security scans are not executed. This behavior is by design to reduce the risk of introducing vulnerabilities into your system.
Example scenarios:
Missing scans on source branches
If security scans are missing on the source branch, GitLab cannot effectively evaluate whether the merge request is introducing new vulnerabilities. In such cases, approval is required as a precautionary measure.
Missing scans on target branches
If security scans are missing on the target branch, GitLab cannot effectively compare the vulnerabilities detected on the source branch. In such cases, any detected vulnerabilities are reported as new.
Projects with no files to scan
Even in projects that contain no files relevant to the selected security scans, the approval requirement is still enforced. This maintains consistent security practices across all projects.
First merge request
The very first merge request in a new project may be blocked if the default branch doesn't have a security scan, even if the source branch has no vulnerabilities.
To resolve these issues:
fallback_behavior with open to prevent invalid or unenforceable rules in a policy from requiring approval.policy tuning setting unblock_rules_using_execution_policies to address scenarios where security scan artifacts are missing, and scan execution policies are enforced. When enabled, this setting makes approval rules optional when scan artifacts are missing from the source branch and a scan is required by a scan execution policy. This feature only works with an existing scan execution policy that has matching scanners. It offers flexibility in the merge request process when certain security scans cannot be performed due to missing artifacts.Target: none in security bot commentsIf you see Target: none in security bot comments, it means GitLab couldn't find a security report for the target branch. To resolve this:
When the target branch has no security scans:
Potential solutions:
fail: open for policies on new projects, but be aware this may allow users to merge vulnerabilities even if scans fail.GitLab.com users may submit a support ticket titled "Merge request approval policy debugging". Provide the following details:
Support teams will investigate logs (pubsub-sidekiq-inf-gprd*) to identify the failure reason. Below is an example response snippet from logs. You can use this query to find logs related to approvals: json.event.keyword: "update_approvals" and json.project_path: "group-path/project-path". Optionally, you can further filter by the merge request identifier using json.merge_request_iid:
"json": {
"project_path": "group-path/project-path",
"merge_request_iid": 2,
"missing_scans": [
"api_fuzzing"
],
"reason": "Scanner removed by MR",
"event": "update_approvals",
}
Search for keywords such as the project-path, api_fuzzing, and merge_request. Example: grep group-path/project-path, and grep merge_request. If you know the correlation ID you can search by correlation ID. For example, if the value of correlation_id is 01HWN2NFABCEDFG, search for 01HWN2NFABCEDFG.
Search in the following files:
/gitlab/gitlab-rails/production_json.log/gitlab/sidekiq/currentCommon failure reasons:
If you notice any inconsistencies in your merge request approval rules, you can take either of the following steps to resynchronize your policies:
resyncSecurityPolicies GraphQL mutation to resynchronize the policies.These actions help ensure that your merge request approval policies are correctly applied and consistent across all merge requests.
If you continue to experience issues with merge request approval policies after taking these steps, contact GitLab support for assistance.
If your policy configuration includes the detected state, merge requests that
fix previously detected vulnerabilities still require approval. The merge request
approval policy evaluates based on vulnerabilities that existed before the changes
in the merge request, which adds an additional layer of review for any changes that affect
known vulnerabilities.
If you want to allow merge requests that fix vulnerabilities to proceed without
any additional approvals due to a detected vulnerability, consider removing the
detected state from your policy configuration.
When a project has merged results pipelines enabled and also runs branch pipelines with security scans, you might experience inconsistencies in the way that merge request approval policies are evaluated in the different pipelines. Consider the following example:
Merge request approval policies evaluate completed pipelines for the latest commit, and the pipeline that completes last is selected for comparison. When the branch pipeline completes after the merged results pipeline, the policy uses the branch pipeline for evaluation.
To avoid this issue:
Run security scans only in merged results pipelines: Configure your security
scanning jobs to run only in merge request pipelines when merged results
pipelines are enabled. Use rules to control
when security jobs run:
sast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Avoid duplicate pipelines: Follow the guidance in avoid duplicate pipelines to ensure security scans run in only one pipeline type per commit.
Use consistent scanner configurations: Run the same scanners with the same pipeline type for both source and target branches.
For more information about duplicate pipelines, see two pipelines when pushing to a branch.