Malicious advisories finder for Dependency Firewall enforcement
## Summary Track the **finder + query layer** that lets the Dependency Firewall (DFW) detect malicious packages **inside the monolith**, at package download/upload time. `Security::DependencyFirewall::FetchPackageMaliciousService` ([!242988](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242988)) is the enforcement entry point, mirroring `FetchPackageVulnerabilitiesService` and `FetchPackageLicensesService`. It is constructed with `(name:, purl_type:, version:)`. Today its `#execute` returns `[]` because there is no query against the malware advisory data. This epic delivers the **`PackageMetadata::MalwareAdvisoriesFinder`** it will call, plus the database indexes that lookup needs, and the feature-flagged rollout. ## Why this replaces the "Malicious Packages API" epic The original approach ([&20341 – Malicious Packages API](https://gitlab.com/groups/gitlab-org/-/work_items/20341)) proposed a **standalone REST API / separate service** for querying malicious packages. That is no longer the direction: the **dependency firewall is part of the monolith**, so enforcement is done in-process through a finder queried by the DFW enforcement services — not via an external API surface. See [&20341 note](https://gitlab.com/groups/gitlab-org/-/work_items/20341#note_3488900393) for context. This epic scopes the concrete Rails work that path requires. ## Data model (already on `master`) The malware advisory tables and models are merged: - `pm_malware_advisories` → `PackageMetadata::MalwareAdvisory` - `pm_malware_affected_packages` → `PackageMetadata::MalwareAffectedPackage` Both live on the **`gitlab_sec_cell_local`** schema (the `sec` database). They are global, non-customer reference data (no organization sharding key), synced from GLAM (GitLab Malware Advisories). ## Scope - **`PackageMetadata::MalwareAdvisoriesFinder`** — a query object constructed with `(name:, purl_type:, version:)` (matching `FetchPackageMaliciousService`). Given a component it returns the `MalwareAffectedPackage` records (with their advisory eager-loaded) that flag `(name, purl_type)` **and whose affected range includes `version`**. The version is matched client-side via `SemverDialects` (`Gitlab::VulnerabilityScanning::AdvisoryUtils#build_matcher`), exactly as `FetchPackageVulnerabilitiesService` does, because malware affected ranges are arbitrary version disjunctions rather than SQL-comparable ranges. A blank version matches all ranges (fail-closed). Audit/return shaping stays in the calling service. - **Query scopes on `PackageMetadata::MalwareAffectedPackage`** — `for_occurrences` / `with_advisory`, mirroring `PackageMetadata::AffectedPackage`. - **Supporting index** — `i_pm_malware_affected_packages_on_purl_type_and_package_name` on `(purl_type, package_name)`. The table currently only has the upsert-unique index (`i_pm_malware_affected_packages_unique_for_upsert`, leading with `pm_malware_advisory_id`), which cannot serve the finder's `(purl_type, package_name)` lookup. (The abbreviated `i_` prefix is required because the full `index_...` name would exceed Postgres's 63-char identifier limit.) - **Wiring** — `FetchPackageMaliciousService#execute` calls the finder and shapes each advisory into its audit/return contract, once [!242988](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242988) lands. - **Feature-flagged rollout** of the enforcement path. ## Implementation plan Ordered work for the finder + service class: 1. **[POC — in review] Finder, scopes, and index** — `PackageMetadata::MalwareAdvisoriesFinder`, the `for_occurrences` / `with_advisory` scopes, and the `(purl_type, package_name)` index. → [!244115](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/244115) 2. **Wire the finder into the service** — replace `FetchPackageMaliciousService#execute`'s `[]` with `MalwareAdvisoriesFinder.new(name:, purl_type:, version:).execute`, mapping each result to the advisory-bearing element shape (`[{ advisory: { id:, source_xid:, ... } }]`) the evaluator/audit path expects. Lands after [!242988](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242988) merges (avoids a cross-MR dependency). 3. **Specs** — finder unit spec (version match incl. blank-version fail-closed, `purl_type`/`package_name` filtering, advisory eager-loading, `SemverDialects::Error` handling) and `FetchPackageMaliciousService` spec. 4. **Feature flag** — gate the finder's DB lookup behind `dependency_firewall_malicious_packages` (default off, `type: wip`, evaluated instance-wide), separate from `dependency_firewall_phase1`. It is a **query kill-switch**: `MalwareAdvisoriesFinder#execute` returns `[]` before querying when the flag is off, so the query can be enabled for performance testing and disabled if it causes production issues. Definition + gate land in the POC MR. → [#605121](https://gitlab.com/gitlab-org/gitlab/-/issues/605121) 5. **Audit event** — thread the triggering advisory into `CreateAuditEventsService` so a block reports what flagged the package. 6. **Rollout & cleanup** — roll out per [#605121](https://gitlab.com/gitlab-org/gitlab/-/issues/605121), then remove the flag. ## Out of scope - The malware data **sync/ingestion** pipeline (tracked separately under the malware-advisories epics). - Malware **rule exclusions** (the vulnerability rule's "exclude these" equivalent) — GA. - Any external/customer-facing API (the superseded &20341 direction). ## References - POC MR: [!244115](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/244115) - Feature flag rollout: [#605121](https://gitlab.com/gitlab-org/gitlab/-/issues/605121) - Enforcement MR: [!242988](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/242988) (epic [&22245 – Dependency firewall - malicious rule evaluator](https://gitlab.com/groups/gitlab-org/-/work_items/22245)) - Superseded: [&20341 – Malicious Packages API](https://gitlab.com/groups/gitlab-org/-/work_items/20341)
epic