Skip to content

Conversation

@timbastin
Copy link
Member

No description provided.

Copilot AI review requested due to automatic review settings January 29, 2026 09:11
@timbastin timbastin closed this Jan 29, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CVE+PURL “unique vulnerability” distribution counts to risk history and uses them in aggregation/badges, alongside introducing a batch endpoint for creating vulnerability events.

Changes:

  • Extend statistics distribution model + DB schema with CVE+PURL risk/CVSS bucket counts and compute/store them during artifact risk aggregation.
  • Update CVSS badge generation to use the new CVE+PURL CVSS counts.
  • Add a new batch API endpoint for applying vulnerability events and adjust grouped vuln counting logic.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
services/statistics_service.go Computes and persists new unique CVE+PURL distribution counts during artifact risk history aggregation.
services/asset_service.go Switches CVSS badge aggregation to use CVE+PURL-unique CVSS counts.
router/dependency_vuln_router.go Adds POST /dependency-vulns/batch/ route to new controller handler.
controllers/dependency_vuln_controller.go Adds batch event creation handler and changes grouped vulnCount to be unique-per-group.
database/models/statistic_model.go Extends Distribution with new CVE+PURL count fields for JSON/ORM mapping.
database/migrations/20260127100000_add_cve_purl_distribution_to_risk_history.*.sql Adds/removes new DB columns and backfills them from existing distribution columns.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 40 to 42
dependencyVulnRouter.POST("/sync/", dependencyVulnController.SyncDependencyVulns, middlewares.NeededScope([]string{"manage"}))
dependencyVulnRouter.POST("/batch/", dependencyVulnController.BatchCreateEvent, middlewares.NeededScope([]string{"manage"}))
dependencyVulnRouter.POST("/:dependencyVulnID/", dependencyVulnController.CreateEvent, middlewares.NeededScope([]string{"manage"}))
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR title is about adding CVE+PURL counts to statistics, but it also introduces a new POST /dependency-vulns/batch/ endpoint and related controller logic. If this endpoint is intentional, please update the PR description/title to reflect the additional API surface change (or split into a separate PR).

Copilot uses AI. Check for mistakes.
Comment on lines +344 to +349
func calculateUniqueCVEPurlCountsByRisk(dependencyVulns []models.DependencyVuln) (low, medium, high, critical int) {
uniqueCombinations := make(map[string]struct{})

for _, vuln := range dependencyVulns {
risk := utils.OrDefault(vuln.RawRiskAssessment, 0)
combinationKey := fmt.Sprintf("%s|%s", vuln.CVEID, vuln.ComponentPurl)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CVE+PURL uniqueness key is built with fmt.Sprintf("%s|%s", ...), which allocates each iteration and can become a hotspot when recomputing histories over many vulns/days. Consider using a small struct as the map key (e.g., {cveID, componentPurl string}) or string concatenation without fmt to reduce allocations.

Suggested change
func calculateUniqueCVEPurlCountsByRisk(dependencyVulns []models.DependencyVuln) (low, medium, high, critical int) {
uniqueCombinations := make(map[string]struct{})
for _, vuln := range dependencyVulns {
risk := utils.OrDefault(vuln.RawRiskAssessment, 0)
combinationKey := fmt.Sprintf("%s|%s", vuln.CVEID, vuln.ComponentPurl)
type cvePurlKey struct {
cveID string
componentPurl string
}
func calculateUniqueCVEPurlCountsByRisk(dependencyVulns []models.DependencyVuln) (low, medium, high, critical int) {
uniqueCombinations := make(map[cvePurlKey]struct{})
for _, vuln := range dependencyVulns {
risk := utils.OrDefault(vuln.RawRiskAssessment, 0)
combinationKey := cvePurlKey{
cveID: vuln.CVEID,
componentPurl: vuln.ComponentPurl,
}

Copilot uses AI. Check for mistakes.
Comment on lines +344 to +347
func calculateUniqueCVEPurlCountsByRisk(dependencyVulns []models.DependencyVuln) (low, medium, high, critical int) {
uniqueCombinations := make(map[string]struct{})

for _, vuln := range dependencyVulns {
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateUniqueCVEPurlCountsByRisk and calculateUniqueCVEPurlCountsByCvss implement the same uniqueness + bucketing logic with only the score source changing (risk vs CVSS). Consider extracting a shared helper that takes a score getter to reduce duplication and avoid future bucket-boundary drift.

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +217
// we need to unique those counts by component_purl + cve_id
// we don't want to count the same vulnerability multiple times for the same package if it only differs in paths

uniqueVulnMap := make(map[string]struct{})
for _, dv := range v.DependencyVulns {
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the count is uniqued by component_purl + cve_id, but the map key is only dv.CVEID. Since entries are already grouped by ComponentPurl, either update the comment to reflect that it's unique-by-CVE within the current package group, or include ComponentPurl in the key to match the comment.

Copilot uses AI. Check for mistakes.
slog.Error("could not find dependencyVuln", "err", err, "vulnID", vulnID)
continue
}

Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BatchCreateEvent reads dependency vulns by arbitrary IDs and applies events without verifying they belong to the current asset/assetVersion from the request context. Validate dependencyVuln.AssetID and dependencyVuln.AssetVersionName against the context (and fail the request on mismatch) to prevent cross-asset modification.

Suggested change
// Ensure the dependency vulnerability belongs to the current asset and asset version
if dependencyVuln.AssetID != asset.ID || dependencyVuln.AssetVersionName != assetVersion.Name {
slog.Error(
"dependencyVuln does not belong to current asset or asset version",
"vulnID", vulnID,
"dependencyVulnAssetID", dependencyVuln.AssetID,
"currentAssetID", asset.ID,
"dependencyVulnAssetVersionName", dependencyVuln.AssetVersionName,
"currentAssetVersionName", assetVersion.Name,
)
return echo.NewHTTPError(400, "one or more vulnerabilities do not belong to the current asset or asset version")
}

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
UPDATE public.artifact_risk_history SET
cve_purl_low = low,
cve_purl_medium = medium,
cve_purl_high = high,
cve_purl_critical = critical,
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This backfill copies values from low/medium/... columns, but those source columns are nullable in the existing table schema. If any existing rows have NULLs, this UPDATE will overwrite the new columns' DEFAULT 0 with NULL. Use COALESCE(low, 0) (etc.) during backfill (or add NOT NULL constraints) to avoid NULLs in the new integer columns.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants