From 4b631f484098e7450c1a2480825e35a940ca42b0 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Wed, 5 Aug 2026 21:03:57 +0300 Subject: [PATCH] Fix CVE-less SCA violations dropped under a Watch convertToCveViolations only converted entries in violation.Cves, and Xray returns a CveDetails entry with an empty cve id (CVSS/CWE still populated) for native XRAY-XXXXX vulnerabilities that have no assigned CVE. Every such entry hit the empty-id guard and the whole violation was dropped, regardless of severity or policy match. Fall back to violation.IssueId when no Cves entry has a usable id, mirroring the same fallback already used when building the SBOM (ExtractIssuesInfoForCdx). Verified live against a real local Xray: a real Watch + Security policy bound to a real indexed NuGet artifact with a real High-severity CVE-less vulnerability (XRAY-1034038, OpenTelemetry.Resources.Host). Before this fix the violation was silently dropped; after, it is returned with full severity/policy/component/fix-version data. Co-Authored-By: Claude Sonnet 5 --- policy/enforcer/policyenforcer.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/policy/enforcer/policyenforcer.go b/policy/enforcer/policyenforcer.go index 013756d58..331fb7909 100644 --- a/policy/enforcer/policyenforcer.go +++ b/policy/enforcer/policyenforcer.go @@ -460,25 +460,21 @@ func convertToBasicViolation(violationType violationutils.ViolationIssueType, vi func convertToCveViolations(cmdResults *results.SecurityCommandResults, violation services.XrayViolation) (cveViolations []violationutils.CveViolation) { resolved, unresolvedCount := resolveInfectedComponents(cmdResults, violation) - for _, cve := range violation.Cves { - if cve.Id == "" { - log.Warn(fmt.Sprintf("Skipping CVE violation with empty CVE ID for violation ID %s", violation.Id)) - continue - } + for _, cveId := range getCveIds(violation) { if len(resolved) == 0 { logComponentLessFallback(violation, unresolvedCount) - cveViolation := createCveViolation(cmdResults, "", cve.Id, violation, nil) + cveViolation := createCveViolation(cmdResults, "", cveId, violation, nil) if cveViolation == nil { - log.Warn(fmt.Sprintf("CVE (%s) violation with no located affected components for violation ID %s", cve.Id, violation.Id)) + log.Warn(fmt.Sprintf("CVE (%s) violation with no located affected components for violation ID %s", cveId, violation.Id)) continue } cveViolations = append(cveViolations, *cveViolation) continue } for i := range resolved { - cveViolation := createCveViolation(cmdResults, resolved[i].xrayId, cve.Id, violation, &resolved[i]) + cveViolation := createCveViolation(cmdResults, resolved[i].xrayId, cveId, violation, &resolved[i]) if cveViolation == nil { - log.Warn(fmt.Sprintf("CVE (%s) violation for component (%s) with no located affected components for violation ID %s", cve.Id, resolved[i].xrayId, violation.Id)) + log.Warn(fmt.Sprintf("CVE (%s) violation for component (%s) with no located affected components for violation ID %s", cveId, resolved[i].xrayId, violation.Id)) continue } cveViolations = append(cveViolations, *cveViolation) @@ -487,6 +483,22 @@ func convertToCveViolations(cmdResults *results.SecurityCommandResults, violatio return cveViolations } +// getCveIds returns the identifiers to convert for a Security violation. Xray-native vulnerabilities +// with no assigned CVE (e.g. XRAY-XXXXX) still report through the CVE-keyed Security violation type, +// with a CveDetails entry whose Id is empty. Fall back to the violation's own issue ID for those, +// mirroring the fallback already used when building the SBOM (see ExtractIssuesInfoForCdx). +func getCveIds(violation services.XrayViolation) (cveIds []string) { + for _, cve := range violation.Cves { + if cve.Id != "" { + cveIds = append(cveIds, cve.Id) + } + } + if len(cveIds) == 0 && violation.IssueId != "" { + cveIds = append(cveIds, violation.IssueId) + } + return +} + func createCveViolation(cmdResults *results.SecurityCommandResults, impactedComponentXrayId, cveId string, violation services.XrayViolation, preResolved *bomResolvedComponent) *violationutils.CveViolation { affectedComponent, scaViolation := convertToScaViolation(cmdResults, impactedComponentXrayId, violation, preResolved) vulnerability, contextualAnalysis := locateBomVulnerabilityInfo(cmdResults, cveId, affectedComponent)