Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Renovate-facing API endpoint that returns a recommended fixed version for a dependency, backed by a new repository/service lookup on stored dependency-vulnerability data.
Changes:
- Expose
GET /api/v1/renovate/recommendation/handled byDependencyVulnController.GetRecommendation. - Introduce a
dtos.Recommendationresponse DTO for external integrations. - Add repository/service plumbing to fetch a recent
direct_dependency_fixed_versionby package name.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| shared/common_interfaces.go | Extends DependencyVulnRepository with a new recommendation lookup method. |
| router/apiv1_router.go | Registers the new Renovate recommendation endpoint under /api/v1. |
| dtos/recommendation_dto.go | Adds the JSON response DTO for recommended version. |
| database/repositories/dependency_vuln_repository.go | Implements DB query to fetch the latest direct-dependency fixed version by package name. |
| controllers/dependency_vuln_controller.go | Adds the HTTP handler and PURL version extraction helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| currentValue := ctx.QueryParam("packageValue") | ||
| if currentValue == "" { | ||
| currentValue = ctx.QueryParam("currentValue") | ||
| } | ||
| if currentValue == "" { | ||
| return echo.NewHTTPError(400, "missing packageValue or currentValue") | ||
| } | ||
|
|
There was a problem hiding this comment.
currentValue is parsed/validated but never used afterwards; this will not compile in Go due to an unused local variable. Either use currentValue (e.g., to decide whether a recommendation should be returned) or remove the variable and its required-query-param validation.
| currentValue := ctx.QueryParam("packageValue") | |
| if currentValue == "" { | |
| currentValue = ctx.QueryParam("currentValue") | |
| } | |
| if currentValue == "" { | |
| return echo.NewHTTPError(400, "missing packageValue or currentValue") | |
| } |
| err := repository.GetDB(ctx, tx). | ||
| WithContext(ctx). | ||
| Table("dependency_vulns"). | ||
| Where("direct_dependency_fixed_version IS NOT NULL AND direct_dependency_fixed_version != ''"). | ||
| Where("EXISTS (SELECT 1 FROM jsonb_array_elements(vulnerability_path) AS purl WHERE purl::text LIKE '%/' || ? || '@%')", packageName, packageName). | ||
| Select("direct_dependency_fixed_version"). | ||
| Order("last_detected DESC"). | ||
| Limit(1). | ||
| Scan(&directDependencyFixedVersion).Error |
There was a problem hiding this comment.
This query expands vulnerability_path with jsonb_array_elements(...) and uses a LIKE predicate, which will force a scan over dependency_vulns and is unlikely to use indexes. Since this endpoint can be called frequently (Renovate), consider a more index-friendly approach (e.g., store extracted package names in a dedicated column/table, or use a JSONB/GiN indexable predicate) to avoid performance degradation as the table grows.
| GetHintsInOrganizationForVuln(ctx context.Context, tx DB, orgID uuid.UUID, pURL string, cveID string) (dtos.DependencyVulnHints, error) | ||
| GetAllByAssetIDAndState(ctx context.Context, tx DB, assetID uuid.UUID, state dtos.VulnState, durationSinceStateChange time.Duration) ([]models.DependencyVuln, error) | ||
| GetDependencyVulnsByOtherAssetVersions(ctx context.Context, tx DB, assetVersionName string, assetID uuid.UUID) ([]models.DependencyVuln, error) | ||
| GetAllVulnsByArtifact(ctx context.Context, tx DB, artifact models.Artifact) ([]models.DependencyVuln, error) | ||
| GetAllVulnsForTagsAndDefaultBranchInAsset(ctx context.Context, tx DB, assetID uuid.UUID, excludedStates []dtos.VulnState) ([]models.DependencyVuln, error) | ||
| // regardless of path. Used for applying status changes to all instances of a CVE+component combination. |
There was a problem hiding this comment.
Adding a new method to DependencyVulnRepository requires regenerating/updating any mocks (e.g., the mockery-generated mocks.DependencyVulnRepository) so tests compile. Please ensure mock generation is rerun and committed as part of this change.
| func (controller DependencyVulnController) GetRecommendation(ctx echo.Context) error { | ||
| packageName := ctx.QueryParam("packageName") | ||
| if packageName == "" { | ||
| packageName = ctx.QueryParam("depName") | ||
| } | ||
| if packageName == "" { | ||
| return echo.NewHTTPError(400, "missing packageName or depName") | ||
| } | ||
|
|
||
| currentValue := ctx.QueryParam("packageValue") | ||
| if currentValue == "" { | ||
| currentValue = ctx.QueryParam("currentValue") | ||
| } | ||
| if currentValue == "" { | ||
| return echo.NewHTTPError(400, "missing packageValue or currentValue") | ||
| } | ||
|
|
||
| recommendedVersion, err := controller.dependencyVulnService.GetDirectDependencyFixedVersionByPackageName(ctx.Request().Context(), packageName) | ||
| if err != nil { | ||
| return echo.NewHTTPError(500, "could not get recommendation").WithInternal(err) | ||
| } | ||
| if recommendedVersion == nil || *recommendedVersion == "" { | ||
| return ctx.JSON(200, dtos.Recommendation{RecommendedVersion: ""}) | ||
| } | ||
|
|
||
| version := extractVersionFromPURL(*recommendedVersion) | ||
|
|
||
| return ctx.JSON(200, dtos.Recommendation{RecommendedVersion: version}) | ||
| } |
There was a problem hiding this comment.
New behavior added via GetRecommendation (Renovate integration endpoint) currently has no tests. There are existing controller tests in the repo (e.g., for CreateEvent), so please add coverage for the expected query-param handling and response shape (including the "no recommendation" case).
| func (repository *dependencyVulnRepository) GetDirectDependencyFixedVersionByPackageName(ctx context.Context, tx *gorm.DB, packageName string) (*string, error) { | ||
| var directDependencyFixedVersion *string | ||
|
|
||
| err := repository.GetDB(ctx, tx). | ||
| WithContext(ctx). | ||
| Table("dependency_vulns"). | ||
| Where("direct_dependency_fixed_version IS NOT NULL AND direct_dependency_fixed_version != ''"). | ||
| Where("EXISTS (SELECT 1 FROM jsonb_array_elements(vulnerability_path) AS purl WHERE purl::text LIKE '%/' || ? || '@%')", packageName, packageName). | ||
| Select("direct_dependency_fixed_version"). | ||
| Order("last_detected DESC"). | ||
| Limit(1). |
There was a problem hiding this comment.
This repository method looks up a recommendation across the entire dependency_vulns table and is not scoped to an org/project/asset. If this data is tenant-specific, this can leak information across tenants (especially since the router exposes it publicly). Consider scoping the query (e.g., by asset/org) or ensuring the returned recommendation is derived from non-tenant-specific data.
| WithContext(ctx). | ||
| Table("dependency_vulns"). | ||
| Where("direct_dependency_fixed_version IS NOT NULL AND direct_dependency_fixed_version != ''"). | ||
| Where("EXISTS (SELECT 1 FROM jsonb_array_elements(vulnerability_path) AS purl WHERE purl::text LIKE '%/' || ? || '@%')", packageName, packageName). |
There was a problem hiding this comment.
LIKE treats % and _ in the parameter as wildcards. If packageName can contain these characters (e.g., underscores are common), this query can match unintended packages and return incorrect recommendations. Consider using an exact-match approach (e.g., strpos(...) > 0 on extracted text) or escaping wildcards in the parameter.
| Where("EXISTS (SELECT 1 FROM jsonb_array_elements(vulnerability_path) AS purl WHERE purl::text LIKE '%/' || ? || '@%')", packageName, packageName). | |
| Where("EXISTS (SELECT 1 FROM jsonb_array_elements(vulnerability_path) AS purl WHERE strpos(purl::text, '/' || ? || '@') > 0)", packageName). |
dbc071f to
1830499
Compare
already tried this out, will do copilot code review now
most testing was done already here 5byuri/renovate-poc#2
TODO: