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
endend 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:
- 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.
- 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 |
|---|---|---|
| `true` | ||
| `true` | ||
| `false` | ||
| `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
- 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.
- After import, run a pipeline on the project to populate vulnerabilities and dependencies.
- 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) ``` - 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 |
|---|---|---|---|
| `true` | |||
| `true` | |||
| `false` | |||
| `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))
Related
- Companion MR (Dependencies GraphQL + REST API): !231747
- Issue: #587647
- Parent epic: gitlab-org#18456