Aggregate security findings from related pipelines in MR widget
What does this MR do and why?
The MR security widget (and the Reports tab security scans view) only displayed findings from the merge request pipeline, while merge request approval policies and the security_policy_pipeline_must_succeed merge check (!229212 (merged)) evaluate the latest pipelines of all sources for the head SHA — for example, both the branch pipeline and the merge request pipeline for the same commit. A merge could be blocked by findings that were never visible in the widget.
Behind the new security_report_related_pipelines feature flag (gitlab_com_derisk, disabled by default):
Security::FindingsFinderaccepts arelated_pipeline_idsparam to include findings from sibling pipelinesVulnerabilities::CompareSecurityReportsServiceaggregates head-report findings across related pipelines (sameSecurity::RelatedPipelinesFindersemantics MRAP evaluation uses), deduplicates findings by UUID, returns:parsingwhile a related pipeline's scans are still being stored, and includes the related pipeline IDs in the reactive cache key so cached comparisons refresh when a related pipeline completes after the MR pipelineenabledSecurityScans/enabledPartialSecurityScansinclude scans from related pipelines so the widget renders the affected report types- The "View all pipeline findings" action is renamed to "View latest pipeline findings" with a clarifying tooltip, since its destination (the pipeline security tab) intentionally remains a single-pipeline view
Performance
The related-pipelines lookup is memoized per request via Gitlab::SafeRequestStore, so one widget load performs a single lookup (2 indexed queries) regardless of how many report types are compared (measured locally: 4 finder executions / 11 queries without the memoization, 1 execution / 2 queries with it). The lookup only runs when the feature flag is enabled and the widget renders.
Raw SQL of the two queries (from local GDK)
-- Query 1: latest completed pipelines per source for the head sha
SELECT DISTINCT ON (source) id FROM (SELECT "p_ci_pipelines".* FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."project_id" = 100088 AND "p_ci_pipelines"."tag" = FALSE AND ("p_ci_pipelines"."source" IN (1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 15) OR "p_ci_pipelines"."source" IS NULL) AND "p_ci_pipelines"."status" IN ('success', 'failed', 'canceled', 'skipped', 'manual') AND "p_ci_pipelines"."sha" = '92a3851cf2b34000265932668d4e0e3ade69d20d' ORDER BY "p_ci_pipelines"."id" DESC LIMIT 1000) AS recent_pipelines ORDER BY source, id DESC;
-- Query 2: hierarchy descendants of the matched pipelines
WITH RECURSIVE "base_and_descendants" AS ((SELECT "p_ci_pipelines"."id", "p_ci_pipelines"."partition_id" FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."project_id" = 100088 AND "p_ci_pipelines"."id" IN (565, 566))
UNION
(SELECT "p_ci_pipelines"."id", "p_ci_pipelines"."partition_id" FROM "p_ci_pipelines", "base_and_descendants", "ci_sources_pipelines" WHERE "ci_sources_pipelines"."pipeline_id" = "p_ci_pipelines"."id" AND "ci_sources_pipelines"."partition_id" = "p_ci_pipelines"."partition_id" AND "ci_sources_pipelines"."source_pipeline_id" = "base_and_descendants"."id" AND "ci_sources_pipelines"."source_partition_id" = "base_and_descendants"."partition_id" AND "ci_sources_pipelines"."source_project_id" = "ci_sources_pipelines"."project_id")) SELECT "p_ci_pipelines"."id" FROM "base_and_descendants" AS "p_ci_pipelines" LIMIT 1000;Both queries are the same shape Security::RelatedPipelinesFinder already executes for approval policy evaluation on every pipeline completion (Security::ScanResultPolicies::UpdateApprovalsService); no schema changes. Query plan posted in discussion: the security_scans index scan (via index_security_scans_on_pipeline_id_and_scan_type) executes in 19.5ms (2.0ms planning, 17.5ms execution).
A MAX_WIDGET_RELATED_PIPELINES = 10 limit was added to cached_ci_and_security_orchestration_pipeline_ids to bound widget load time relative to commit pipeline count. Policy evaluation retains its separate PIPELINES_LIMIT = 1_000 bound.
Screenshots or screen recordings
MR with SAST running on the branch (push) pipeline only and secret detection on the MR pipeline only, for the same head SHA:
| State | Widget |
|---|---|
| Feature flag disabled (before) | enabledSecurityScans.sast: false, SAST comparer added: 0, secret detection added: 1 |
| Feature flag enabled (after) | enabledSecurityScans.sast: true, SAST comparer added: 5, secret detection added: 1 |
Widget with the flag enabled — union of both pipelines' findings:
Reports tab with the renamed action and clarifying tooltip:
How to set up and validate locally
- Enable the feature flag:
Feature.enable(:security_report_related_pipelines) - In an Ultimate project, configure
.gitlab-ci.ymlso one scanner runs onpushpipelines only (for example SAST) and another on merge request pipelines only (for example secret detection) - Open an MR that produces findings on both pipelines and wait for both pipelines to complete
- The MR security widget and Reports tab show the union of findings from both pipelines for the head SHA; with the flag disabled, only the MR pipeline findings are shown
References
- Closes #599102
- Backend gate that already evaluates both pipelines: !229212 (merged)

