Exclude withdrawn malware advisories from SBOM and CVS matching
Summary
Exclude malware advisories with a non-null withdrawn_date from the query surface used to match SBOM components and occurrences against malware affected packages, so neither the SBOM/CI path nor CVS can create findings from a retracted advisory.
Problem
pm_malware_advisories.withdrawn_date is nullable; a non-null value means the advisory was retracted upstream. Neither consumer path should create new findings from a withdrawn advisory.
Both paths query through PackageMetadata::MalwareAffectedPackage.for_occurrences(...).with_advisory or code built on it. If the withdrawn check is added independently in each caller, it is easy to implement in one and miss the other.
Proposal
Add the exclusion once, in the shared query layer, rather than in each caller. Either fold it into the existing scopes or add a composable scope such as not_withdrawn filtering on pm_malware_advisories.withdrawn_date IS NULL.
Because the SBOM/CI matching in SecurityReportBuilder and the CVS scanner both sit on top of this same query surface, adding it here means neither path can regress independently.
Resolving vulnerabilities that were already created before an advisory was withdrawn is out of scope and tracked separately.
Acceptance criteria
- A single shared scope or condition excludes affected packages whose malware advisory has a non-null
withdrawn_date. - The exclusion applies wherever
for_occurrences/with_advisoryis used, without duplication in callers. - Spec: a component matching only a withdrawn advisory produces no match.
- Spec: a component matching a non-withdrawn advisory still matches.
- Already-created vulnerabilities are untouched by this change.
Technical notes
Models: PackageMetadata::MalwareAdvisory and PackageMetadata::MalwareAffectedPackage (ee/app/models/package_metadata/). Note that PackageMetadata::MalwareAdvisoriesFinder, used by the Dependency Firewall, goes through the same scopes, so consider whether that path should share the exclusion or keep its current behaviour.
Land this before or alongside the matching work in either consumer path.
Parent epic: &21156