Draft: Vulnerability bulk confirm mutation

What does this MR do and why?

This change introduces a new mutation (Mutation.VulnerabilitiesConfirm) to confirm multiple vulnerabilities at the same time. It accepts a maximum of 100 vulnerability id's at a time and performs bulk updates and inserts into the vulnerabilities, vulnerability_state_transitions, and vulnerability_reads tables.

SELECT DISTINCT
    "vulnerabilities"."project_id"
FROM
    "vulnerabilities"
WHERE
    "vulnerabilities"."id" IN (
        649, 648, 647, 646, 645, 644, 643, 641, 640, 639,
        638, 636, 635, 634, 632, 631, 630, 629, 628, 627,
        626, 625, 624, 623, 621, 620, 619, 618, 616, 615,
        614, 613, 612, 611, 610, 609, 607, 606, 605, 604,
        603, 602, 601, 600, 599, 598, 597, 596, 595, 593,
        592, 591, 590, 589, 588, 587, 586, 584, 583, 582,
        581, 580, 579, 578, 577, 576, 575, 574, 572, 571,
        570, 569, 568, 567, 566, 564, 563, 562, 561, 560,
        559, 558, 557, 556, 555, 554, 553, 552, 551, 550,
        549, 547, 546, 545, 541
    );
SELECT
  "vulnerabilities"."id",
  "vulnerabilities"."state",
  "vulnerabilities"."project_id"
FROM
  "vulnerabilities"
WHERE
  "vulnerabilities"."id" IN (
    649, 648, 647, 646, 645, 644, 643, 641, 640, 639, 638, 636, 635, 634, 632,
    631, 630, 629, 628, 627, 626, 625, 624, 623, 621, 620, 619, 618, 616, 615,
    614, 613, 612, 611, 610, 609, 607, 606, 605, 604, 603, 602, 601, 600, 599,
    598, 597, 596, 595, 593, 592, 591, 590, 589, 588, 587, 586, 584, 583, 582,
    581, 580, 579, 578, 577, 576, 575, 574, 573, 572, 571, 570, 569, 568, 567,
    566, 564, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551,
    550, 549, 547, 546, 545, 541
  );
INSERT INTO "vulnerability_state_transitions" (
  "vulnerability_id", "from_state", "to_state", "comment", "author_id", "created_at", "updated_at"
)
VALUES
  (545, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (546, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (549, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (553, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (554, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (555, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (557, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (561, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (570, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (581, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (584, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (586, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (588, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (589, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (595, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (599, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (600, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (604, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (609, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (610, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (612, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (616, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (618, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (629, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (634, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (636, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (639, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (640, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (641, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (643, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (646, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (647, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (648, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (649, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (551, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (558, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (559, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (560, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (564, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (566, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (567, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (568, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (571, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (573, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (574, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (577, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (578, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (579, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (590, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (591, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (592, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (596, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (597, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (598, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (601, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (602, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (603, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (605, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (606, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (607, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (613, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (619, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (621, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (623, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (625, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (626, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (627, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (628, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (630, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (631, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (632, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (635, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (644, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (550, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (552, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (556, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (562, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (563, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (569, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (572, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (575, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (576, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (580, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (582, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (583, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (587, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (593, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (611, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (614, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (615, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (620, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (624, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (638, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (645, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (541, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876'),
  (547, 4, 4, 'Test comment', 1, '2025-07-04 00:22:17.029876', '2025-07-04 00:22:17.029876')
RETURNING
  "id";
UPDATE
  "vulnerability_reads"
SET
  "auto_resolved" = FALSE
WHERE
  "vulnerability_reads"."vulnerability_id" IN (
    SELECT
      "vulnerabilities"."id"
    FROM
      "vulnerabilities"
    WHERE
      "vulnerabilities"."id" IN (
        649, 648, 647, 646, 645, 644, 643, 641, 640, 639, 638, 636, 635, 634, 632,
        631, 630, 629, 628, 627, 626, 625, 624, 623, 621, 620, 619, 618, 616, 615,
        614, 613, 612, 611, 610, 609, 607, 606, 605, 604, 603, 602, 601, 600, 599,
        598, 597, 596, 595, 593, 592, 591, 590, 589, 588, 587, 586, 584, 583, 582,
        581, 580, 579, 578, 577, 576, 575, 574, 573, 572, 571, 570, 569, 568, 567,
        566, 564, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551,
        550, 549, 547, 546, 545, 541
      )
  );
UPDATE
  "vulnerabilities"
SET
  "state" = 4,
  "auto_resolved" = FALSE,
  "confirmed_by_id" = 1,
  "confirmed_at" = '2025-07-04 00:22:17.029876',
  "updated_at" = '2025-07-04 00:22:17.029876',
  "dismissed_at" = NULL,
  "dismissed_by_id" = NULL,
  "resolved_at" = NULL,
  "resolved_by_id" = NULL
WHERE
  "vulnerabilities"."id" IN (
    649, 648, 647, 646, 645, 644, 643, 641, 640, 639, 638, 636, 635, 634, 632,
    631, 630, 629, 628, 627, 626, 625, 624, 623, 621, 620, 619, 618, 616, 615,
    614, 613, 612, 611, 610, 609, 607, 606, 605, 604, 603, 602, 601, 600, 599,
    598, 597, 596, 595, 593, 592, 591, 590, 589, 588, 587, 586, 584, 583, 582,
    581, 580, 579, 578, 577, 576, 575, 574, 573, 572, 571, 570, 569, 568, 567,
    566, 564, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551,
    550, 549, 547, 546, 545, 541
  );

How to set up and validate locally

  1. Enable the feature flag:
  • bundle exec rails c
  • Feature.enable(:confirm_multiple_vulnerabilities)
  1. Run the mutation in http://localhost:3000/-/graphql-explorer:

Firstly, enter the vulnerability ids into the variables field:

{
  "comment": "Test comment",
  "ids" : [
    "gid://gitlab/Vulnerability/649",
    "gid://gitlab/Vulnerability/648",
    ...
  ]
}

Then, execute.

mutation BulkConfirmVulnerabilities($ids: [VulnerabilityID!]!, $comment: String!) {
  vulnerabilitiesConfirm(input: {
    vulnerabilityIds: $ids,
    comment: $comment,
  }) {
    vulnerabilities {
      id
      state
      confirmedAt
      confirmedBy {
        name
      }
      stateTransitions {
        nodes {
          fromState
          toState
          comment
          author {
            name
          }
        }
      }
    }
    errors
  }
}

Screenshots or screen recordings

When the feature flag is disabled: image

When the feature flag is enabled and executed with valid arguments: image

When > 100 vulnerabilities are confirmed: image

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 #395677

Edited by Patrick He

Merge request reports

Loading