Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions controllers/vulndb_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"
"strings"

"github.com/l3montree-dev/devguard/config"
"github.com/l3montree-dev/devguard/database/models"
"github.com/l3montree-dev/devguard/dtos"
"github.com/l3montree-dev/devguard/normalize"
Expand All @@ -16,14 +17,16 @@ import (
)

type VulnDBController struct {
cveRepository shared.CveRepository
maliciousPackageChecker shared.MaliciousPackageChecker
cveRepository shared.CveRepository
maliciousPackageChecker shared.MaliciousPackageChecker
affectedComponentRepository shared.AffectedComponentRepository
}

func NewVulnDBController(cveRepository shared.CveRepository, maliciousPackageChecker shared.MaliciousPackageChecker) *VulnDBController {
func NewVulnDBController(cveRepository shared.CveRepository, maliciousPackageChecker shared.MaliciousPackageChecker, affectedComponentRepository shared.AffectedComponentRepository) *VulnDBController {
return &VulnDBController{
cveRepository: cveRepository,
maliciousPackageChecker: maliciousPackageChecker,
cveRepository: cveRepository,
maliciousPackageChecker: maliciousPackageChecker,
affectedComponentRepository: affectedComponentRepository,
}
}

Expand Down Expand Up @@ -150,3 +153,47 @@ func (c VulnDBController) PURLInspect(ctx shared.Context) error {
MaliciousPackage: maliciousPackage,
})
}

type ecosystemRow struct {
Ecosystem string `gorm:"ecosystem" json:"ecosystem"`
Count int `gorm:"count" json:"count"`
}

// return the number of affected packages by ecosystem
func (c VulnDBController) GetEcosystemDistribution(ctx shared.Context) error {
Comment thread
Hubtrick-Git marked this conversation as resolved.
results := make([]ecosystemRow, 1024)
Comment thread
Hubtrick-Git marked this conversation as resolved.

// static sql to get amount of packages by ecosystem
sql := `SELECT ecosystem, COUNT(*) FROM affected_components GROUP BY ecosystem;`
err := c.affectedComponentRepository.GetDB(nil).Raw(sql).Find(&results).Error
Comment on lines +167 to +168
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The query SELECT ecosystem, COUNT(*) FROM affected_components GROUP BY ecosystem performs a full table scan and aggregation on the affected_components table, which could become slow as the table grows. Consider these optimizations:

  1. Add an index on the ecosystem column if one doesn't already exist
  2. Consider caching this result since ecosystem distribution is relatively static data
  3. If the table is very large, consider using approximate counts or maintaining a materialized view

Since this appears to be a public endpoint (no authentication middleware on the vulndb router), performance is especially important to prevent potential DoS via repeated expensive queries.

Copilot uses AI. Check for mistakes.
if err != nil {
return err
Comment thread
Hubtrick-Git marked this conversation as resolved.
Outdated
}

// since ecosystem have tags behind the : character we want to group them by their prefix
jsonResults := buildResultsJSON(results)

return ctx.String(200, jsonResults)
}
Comment thread
Hubtrick-Git marked this conversation as resolved.

// group ecosystem by prefix ecosystem string and return the equivalent json encoding
func buildResultsJSON(rows []ecosystemRow) string {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just marshal the map 🙂

// map to deduplicate ecosystem with different tags
aggregatedResults := make(map[string]int)

// fill the map with the value of the rows
for _, row := range rows {
before, _, _ := strings.Cut(row.Ecosystem, ":")
aggregatedResults[before] += row.Count
}

// build the json encoding
jsonString := "{\n"
for ecosystem, count := range aggregatedResults {
jsonString += fmt.Sprintf("%s\"%s\": %d,\n", config.PrettyJSONIndent, ecosystem, count)
}

jsonString, _ = strings.CutSuffix(jsonString, ",\n")
jsonString += "\n}"
return jsonString
}
1 change: 1 addition & 0 deletions router/vulndb_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func NewVulnDBRouter(apiV1Router APIV1Router, vulndbController *controllers.Vuln
cveRouter.GET("/", vulndbController.ListPaged)
cveRouter.GET("/:cveID/", vulndbController.Read)
cveRouter.GET("/purl-inspect/:purl", vulndbController.PURLInspect)
cveRouter.GET("/affected-package-distribution/", vulndbController.GetEcosystemDistribution)
return VulnDBRouter{
Group: cveRouter,
}
Expand Down
Loading