Add dep. management security update merge request creation service
What does this MR do and why?
Adds DependencyManagement::SecurityUpdate::CreateMergeRequestService and its supporting OutputParser, which together handle the automated creation of merge requests after a managed dependency security update pipeline completes.
This is part of the Auto-Remediation feature for Dependency Management. When a security vulnerability is detected in a project's dependencies, an orchestrator pipeline bumps the affected packages to patched versions. This service takes the pipeline's output.json artifact, parses the dependency changes and updated file contents, commits them to a new branch, and opens (or updates) a merge request targeting the project's default branch. The merge request is authored by the dependency management service account and is automatically linked to the originating vulnerability.
This MR implements the service only (MR 1 of the implementation plan). A follow-up MR will add the worker that listens for Ci::PipelineFinishedEvent and invokes this service.
Stacked Diffs
- master
- Add security update workload creation service (!223849 - merged)
- Add dependency management security update sched... (!224996 - merged)
- Add Dependency Management Service Account Service (!228340)
- Add dep. management security update merge reque... (!225540)
- auto remediation merge request creation worker
- Add dep. management security update merge reque... (!225540)
- Add security update workload creation service (!223849 - merged)
References
Screenshots or screen recordings
MR created by GitLab Dependency Management Service Account:
MR is linked to the vulnerability:
How to set up and validate locally
You can create the script below and execute it locally in the rails console. Alternatively, you can execute it line by line in console as well.
Please let me know if you encounter any issues with it.
create_mr.rb
# ============================================================================
# Local validation for CreateMergeRequestService
#
# Run in rails console: load 'path/to/this/script.rb'
#
# Prerequisites:
# - GDK with Ultimate license
# - A project with a detected vulnerability that has SBOM data
# - Adjust PROJECT_PATH and VULNERABILITY_ID below
# ============================================================================
PROJECT_PATH = 'your-group/your-project'
VULNERABILITY_ID = 772
# ── Setup ───────────────────────────────────────────────────────────────────
project = Project.find_by_full_path(PROJECT_PATH)
vulnerability = Vulnerability.find(VULNERABILITY_ID)
occurrence = vulnerability.sbom_occurrences.first
dep_name = occurrence.component_name.downcase.gsub(/[^a-z0-9]/, '-').squeeze('-').gsub(/^-|-$/, '')
major = occurrence.version.to_s.match(/^(\d+)/)&.captures&.first || '0'
target_branch = "dependency-management/#{dep_name}-#{major}.x"
# ── Cleanup previous runs ──────────────────────────────────────────────────
if project.repository.branch_exists?(target_branch)
project.repository.delete_branch(target_branch)
project.repository.expire_branches_cache
end
existing_mr = project.merge_requests.opened.find_by(source_branch: target_branch)
existing_mr&.close!
Vulnerabilities::MergeRequestLink.where(vulnerability: vulnerability).delete_all
# ── Fake pipeline ───────────────────────────────────────────────────────────
pipeline = Ci::Pipeline.create!(
project: project, ref: project.default_branch, sha: project.commit.sha,
source: :push, status: :success, partition_id: Ci::Pipeline.current_partition_value
)
stage = Ci::Stage.create!(
pipeline: pipeline, project: project, name: 'build', position: 1,
partition_id: pipeline.partition_id
)
Ci::Build.create!(
pipeline: pipeline, project: project, name: 'dependency-update',
status: :success, ref: project.default_branch,
partition_id: pipeline.partition_id, ci_stage: stage, scheduling_type: :stage
)
Ci::PipelineVariable.create!(
pipeline: pipeline, key: 'DEPENDENCY_MANAGEMENT_TARGET_BRANCH',
value: target_branch, partition_id: pipeline.partition_id
)
# ── Fake orchestrator output ────────────────────────────────────────────────
new_version = '2.5.33'
output_json = {
'dependencies' => [
{ 'name' => occurrence.component_name, 'version' => new_version,
'previous-version' => occurrence.version.to_s }
],
'updated-files' => [
{ 'path' => occurrence.input_file_path,
'content' => "# Updated by GitLab Dependency Management\n#{occurrence.component_name}:#{new_version}\n",
'encoding' => 'text' }
]
}.to_json
# ── Execute ─────────────────────────────────────────────────────────────────
service = DependencyManagement::SecurityUpdate::CreateMergeRequestService.new(
project: project, pipeline: pipeline, vulnerability: vulnerability
)
service.instance_variable_set(:@_test_output, output_json)
def service.fetch_and_parse_output
DependencyManagement::SecurityUpdate::OutputParser.parse(@_test_output)
end
result = service.execute
# ── Result ──────────────────────────────────────────────────────────────────
if result.success?
mr = result.payload[:merge_request]
# Force diff generation (normally async via Sidekiq)
mr.ensure_merge_request_diff
mr.merge_request_diff.save!
MergeRequests::AfterCreateService.new(project: project, current_user: mr.author).execute(mr)
mr.reload
url = Gitlab::Routing.url_helpers.project_merge_request_url(project, mr)
puts "✅ Open in browser: #{url}"
else
puts "❌ #{result.message}"
endMR 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.

