Add malware field to Vulnerability GraphQL API

Summary

Part of gitlab-org/gitlab#587647 — Phase 1 backend requirement for the Malicious Package UI Representation and Filters epic.

Adds a nullable malware: Boolean field to:

  • VulnerabilityType (GraphQL — used by both project- and group-level Vulnerability Reports)

Note: The companion MR for the Dependencies GraphQL API and REST API is !231747.

Approach

Detection logic

All surfaces delegate to a single class method:

MalwareDetection concern

```ruby

ee/app/models/concerns/vulnerabilities/malware_detection.rb

module Vulnerabilities module MalwareDetection extend ActiveSupport::Concern

class_methods do
  def malware_status_for(vulnerabilities, project)
    return true if vulnerabilities&.any?(&:has_glam_identifier?)

    sscs_addon_active_for?(project) ? false : nil
  end

  def sscs_addon_active_for?(project)
    return false unless project

    Gitlab::SafeRequestStore.fetch("sscs_addon_active_for?:#{project.id}") do
      project.sscs_malware_detection_feature_flag_enabled?
    end
  end
end

end end ```

The concern is included in the `Vulnerability` model. The `malware?` instance method on `Vulnerability` delegates to `Vulnerability.malware_status_for([self], project)`.

The two-step logic:

  1. Check `identifier_names` on each linked `vulnerability_read` for any entry starting with `GLAM-`. If found, return `true` unconditionally — GLAM-* identifiers are the source of truth regardless of add-on status.
  2. If no GLAM identifier, gate on the SSCS feature flag:
    • Feature flag enabled → return `false` (definitively not malware)
    • Feature flag disabled → return `nil` (determination not possible)

`identifier_names` is a denormalized array column on `vulnerability_reads`, populated at ingestion time — making this a zero-join read. The `sscs_addon_active_for?` result is memoised per project per request via `Gitlab::SafeRequestStore`.

Add-on gating

The SSCS malware detection feature is gated via the WIP feature flag `sscs_malware_detection` only. Per fulfillment team feedback, the `AddOn` enum and `AddOnPurchase` check have been removed to avoid committing to a DB schema before add-on requirements are finalized. The proper add-on gating will be added by the fulfillment team once provisioned.

Constants

One constant is added to the `Vulnerability` model as the single source of truth:

```ruby MALWARE_PACKAGE_IDENTIFIER_PREFIX = 'GLAM-' ```

Field values

Value Meaning
`true` Malware package detected (GLAM identifier present)
`false` Not a malware package (feature flag enabled, no GLAM identifier)
`null` Feature flag disabled — determination not possible

Behaviour breakdown

Expected malware field values per scenario

Vulnerabilities GraphQL (`malware` on `VulnerabilityType`)

GLAM Identifier Present Feature Flag Enabled Expected `malware` value
Yes Yes `true`
Yes No `true`
No Yes `false`
No No `null`

Database

No new database queries are introduced by this MR. The `malware` field on `VulnerabilityType` resolves via `vulnerability.malware?` which calls `Vulnerability.malware_status_for([self], project)`. The vulnerability and its `vulnerability_read` are already loaded as part of the standard vulnerability query — no additional preloading is required.

The add-on gating is now feature-flag-only (no `AddOnPurchase` query), so the only DB access is reading the already-loaded `identifier_names` column from `vulnerability_reads`.

Local testing

Setup steps, queries, and verification screenshots

Setup

  1. Import the project https://gitlab.com/gitlab-org/govern/threat-insights-demos/verification-projects/bala-test-group/test-malicious-dependency-badge into your GDK. This project stubs the dependency scanner report with GLAM identifier vulnerabilities.
  2. After import, run a pipeline on the project to populate vulnerabilities and dependencies.
  3. The `sscs_malware_detection` feature flag is enabled by default (WIP type). To test the `null` scenario (feature flag disabled), disable it: ```ruby

    In rails console

    Feature.disable(:sscs_malware_detection) ``` To re-enable: ```ruby Feature.enable(:sscs_malware_detection) ```
  4. Use the queries below to verify the `malware` field.

Vulnerabilities GraphQL — project level

Query

```graphql { project(fullPath: "bala-test-group/test-data-project-for-filtering-with-es") { vulnerabilities { nodes { id uuid name malware } } } } ```

GLAM Identifier Present Feature Flag Enabled Expected `malware` Screenshot
Yes Yes `true`
Yes No `true`
No Yes `false`
No No `null`

Files changed

File Change
`ee/app/models/concerns/vulnerabilities/malware_detection.rb` New — `MalwareDetection` concern with `malware_status_for` and `sscs_addon_active_for?` (FF-gated)
`ee/app/models/ee/vulnerability.rb` Include concern, add `malware?` instance method and `MALWARE_PACKAGE_IDENTIFIER_PREFIX` constant
`ee/app/graphql/types/vulnerability_type.rb` Add `malware` field (experiment, milestone 19.0); delegates to `malware?`
`app/models/group.rb` Add `sscs_malware_detection_feature_flag_enabled?` helper
`app/models/project.rb` Add `sscs_malware_detection_feature_flag_enabled?` helper
`config/feature_flags/wip/sscs_malware_detection.yml` New — WIP feature flag (milestone 19.0, default disabled)
`ee/spec/models/concerns/vulnerabilities/malware_detection_spec.rb` New — full spec coverage for the concern
`ee/spec/models/ee/vulnerability_spec.rb` Add `malware?` delegation test
`ee/spec/graphql/types/vulnerability_type_spec.rb` Add `malware` field specs: GLAM detection, FF gating, nil vulnerability_read, N+1
`doc/api/graphql/reference/_index.md` Regenerated GraphQL reference docs

Follow-up issues

  • Replace the temporary feature flag gate with proper add-on provisioning once finalized by the fulfillment team (#596723 (closed))
Edited by Bala Kumar

Merge request reports

Loading