Skip to content

Commit a986e01

Browse files
authored
Merge pull request #954 from l3montree-dev/add-basic-risk-handling-information-sharing-inside-org
Added hints about other instances of the same vuln in other parts of organization
2 parents 62f3d24 + e977b06 commit a986e01

10 files changed

Lines changed: 295 additions & 89 deletions

File tree

cmd/devguard/api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ func BuildRouter(db core.DB) *echo.Echo {
729729
dependencyVulnRouter.POST("/:dependencyVulnID/", dependencyVulnController.CreateEvent, neededScope([]string{"manage"}), projectScopedRBAC(core.ObjectAsset, core.ActionUpdate))
730730
dependencyVulnRouter.POST("/:dependencyVulnID/mitigate/", dependencyVulnController.Mitigate, neededScope([]string{"manage"}), projectScopedRBAC(core.ObjectAsset, core.ActionUpdate))
731731
dependencyVulnRouter.GET("/:dependencyVulnID/events/", vulnEventController.ReadAssetEventsByVulnID)
732+
dependencyVulnRouter.GET("/:dependencyVulnID/hints/", dependencyVulnController.Hints)
732733

733734
firstPartyVulnRouter := assetVersionRouter.Group("/first-party-vulns")
734735
firstPartyVulnRouter.GET("/", firstPartyVulnController.ListPaged)

internal/common/cve_obj.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,12 @@ func (r RiskCalculationReport) String() string {
6666
}
6767
return string(str)
6868
}
69+
70+
// used to return information about other instances of a dependency vuln in other parts of an organization
71+
type DependencyVulnHints struct {
72+
AmountOpen int `json:"amountOpen"`
73+
AmountFixed int `json:"amountFixed"`
74+
AmountAccepted int `json:"amountAccepted"`
75+
AmountFalsePositive int `json:"amountFalsePositive"`
76+
AmountMarkedForTransfer int `json:"amountMarkedForTransfer"`
77+
}

internal/core/common_interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ type DependencyVulnRepository interface {
148148
ApplyAndSave(tx DB, dependencyVuln *models.DependencyVuln, vulnEvent *models.VulnEvent) error
149149
GetDependencyVulnsByDefaultAssetVersion(tx DB, assetID uuid.UUID, scannerID string) ([]models.DependencyVuln, error)
150150
ListUnfixedByAssetAndAssetVersionAndScannerID(assetVersionName string, assetID uuid.UUID, scannerID string) ([]models.DependencyVuln, error)
151+
GetHintsInOrganizationForVuln(tx DB, orgID uuid.UUID, pURL string, cveID string) (common.DependencyVulnHints, error)
151152
}
152153

153154
type FirstPartyVulnRepository interface {

internal/core/org/org_dto.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ type patchRequest struct {
7878
Grundschutz *bool `json:"grundschutz"`
7979
Description *string `json:"description"`
8080

81-
IsPublic *bool `json:"isPublic"`
82-
ConfigFiles *map[string]any `json:"configFiles"`
83-
Language *string `json:"language"`
81+
ShareVulnInformation *bool `json:"shareVulnInformation"`
82+
IsPublic *bool `json:"isPublic"`
83+
ConfigFiles *map[string]any `json:"configFiles"`
84+
Language *string `json:"language"`
8485
}
8586

8687
func (p patchRequest) applyToModel(org *models.Org) bool {
@@ -137,6 +138,11 @@ func (p patchRequest) applyToModel(org *models.Org) bool {
137138
org.Description = *p.Description
138139
}
139140

141+
if p.ShareVulnInformation != nil {
142+
updated = true
143+
org.SharesVulnInformation = *p.ShareVulnInformation
144+
}
145+
140146
if p.IsPublic != nil {
141147
updated = true
142148
org.IsPublic = *p.IsPublic
@@ -177,9 +183,9 @@ type OrgDTO struct {
177183

178184
JiraIntegrations []common.JiraIntegrationDTO `json:"jiraIntegrations" gorm:"foreignKey:OrgID;"`
179185

180-
Webhooks []common.WebhookIntegrationDTO `json:"webhooks" gorm:"foreignKey:OrgID;"`
181-
182-
IsPublic bool `json:"isPublic" gorm:"default:false;"`
186+
SharesVulnInformation bool `json:"sharesVulnInformation"`
187+
IsPublic bool `json:"isPublic" gorm:"default:false;"`
188+
Webhooks []common.WebhookIntegrationDTO `json:"webhooks" gorm:"foreignKey:OrgID;"`
183189

184190
ConfigFiles map[string]any `json:"configFiles"`
185191

@@ -231,6 +237,7 @@ func fromModel(org models.Org) OrgDTO {
231237
Grundschutz: org.Grundschutz,
232238
Slug: org.Slug,
233239
Description: org.Description,
240+
SharesVulnInformation: org.SharesVulnInformation,
234241
IsPublic: org.IsPublic,
235242

236243
Projects: org.Projects,

internal/core/vuln/dependency_vuln_controller.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ func NewHTTPController(dependencyVulnRepository core.DependencyVulnRepository, d
4646
}
4747
}
4848

49-
func (c dependencyVulnHTTPController) ListByOrgPaged(ctx core.Context) error {
49+
func (controller dependencyVulnHTTPController) ListByOrgPaged(ctx core.Context) error {
5050

51-
userAllowedProjectIds, err := c.projectService.ListAllowedProjects(ctx)
51+
userAllowedProjectIds, err := controller.projectService.ListAllowedProjects(ctx)
5252
if err != nil {
5353
return echo.NewHTTPError(500, "could not get projects").WithInternal(err)
5454
}
5555

56-
pagedResp, err := c.dependencyVulnRepository.GetDefaultDependencyVulnsByOrgIDPaged(
56+
pagedResp, err := controller.dependencyVulnRepository.GetDefaultDependencyVulnsByOrgIDPaged(
5757
nil,
5858

5959
utils.Map(userAllowedProjectIds, func(p models.Project) string {
@@ -73,10 +73,10 @@ func (c dependencyVulnHTTPController) ListByOrgPaged(ctx core.Context) error {
7373
}))
7474
}
7575

76-
func (c dependencyVulnHTTPController) ListByProjectPaged(ctx core.Context) error {
76+
func (controller dependencyVulnHTTPController) ListByProjectPaged(ctx core.Context) error {
7777
project := core.GetProject(ctx)
7878

79-
pagedResp, err := c.dependencyVulnRepository.GetDefaultDependencyVulnsByProjectIDPaged(
79+
pagedResp, err := controller.dependencyVulnRepository.GetDefaultDependencyVulnsByProjectIDPaged(
8080
nil,
8181
project.ID,
8282

@@ -95,13 +95,13 @@ func (c dependencyVulnHTTPController) ListByProjectPaged(ctx core.Context) error
9595
}))
9696
}
9797

98-
func (c dependencyVulnHTTPController) ListPaged(ctx core.Context) error {
98+
func (controller dependencyVulnHTTPController) ListPaged(ctx core.Context) error {
9999
// get the asset
100100
assetVersion := core.GetAssetVersion(ctx)
101101

102102
// check if we should list flat - this means not grouped by package
103103
if ctx.QueryParam("flat") == "true" {
104-
dependencyVulns, err := c.dependencyVulnRepository.GetDependencyVulnsByAssetVersionPagedAndFlat(nil, assetVersion.Name, assetVersion.AssetID, core.GetPageInfo(ctx), ctx.QueryParam("search"), core.GetFilterQuery(ctx), core.GetSortQuery(ctx))
104+
dependencyVulns, err := controller.dependencyVulnRepository.GetDependencyVulnsByAssetVersionPagedAndFlat(nil, assetVersion.Name, assetVersion.AssetID, core.GetPageInfo(ctx), ctx.QueryParam("search"), core.GetFilterQuery(ctx), core.GetSortQuery(ctx))
105105
if err != nil {
106106
return echo.NewHTTPError(500, "could not get dependencyVulns").WithInternal(err)
107107
}
@@ -111,7 +111,7 @@ func (c dependencyVulnHTTPController) ListPaged(ctx core.Context) error {
111111
}))
112112
}
113113

114-
pagedResp, packageNameIndexMap, err := c.dependencyVulnRepository.GetByAssetVersionPaged(
114+
pagedResp, packageNameIndexMap, err := controller.dependencyVulnRepository.GetByAssetVersionPaged(
115115
nil,
116116
assetVersion.Name,
117117
assetVersion.AssetID,
@@ -191,7 +191,7 @@ func (c dependencyVulnHTTPController) ListPaged(ctx core.Context) error {
191191
return ctx.JSON(200, core.NewPaged(core.GetPageInfo(ctx), pagedResp.Total, values))
192192
}
193193

194-
func (c dependencyVulnHTTPController) Mitigate(ctx core.Context) error {
194+
func (controller dependencyVulnHTTPController) Mitigate(ctx core.Context) error {
195195
type justification struct {
196196
Comment string `json:"comment"`
197197
}
@@ -218,23 +218,23 @@ func (c dependencyVulnHTTPController) Mitigate(ctx core.Context) error {
218218
}
219219

220220
// fetch the dependencyVuln again from the database. We do not know anything what might have changed. The third party integrations might have changed the state of the dependency_vuln.
221-
dependencyVuln, err := c.dependencyVulnRepository.Read(dependencyVulnID)
221+
dependencyVuln, err := controller.dependencyVulnRepository.Read(dependencyVulnID)
222222
if err != nil {
223223
return echo.NewHTTPError(404, "could not find dependencyVuln")
224224
}
225225

226226
return ctx.JSON(200, convertToDetailedDTO(dependencyVuln))
227227
}
228228

229-
func (c dependencyVulnHTTPController) Read(ctx core.Context) error {
229+
func (controller dependencyVulnHTTPController) Read(ctx core.Context) error {
230230

231231
dependencyVulnID, _, err := core.GetVulnID(ctx)
232232
if err != nil {
233233
return echo.NewHTTPError(400, "invalid dependencyVuln id")
234234
}
235235
asset := core.GetAsset(ctx)
236236

237-
dependencyVuln, err := c.dependencyVulnRepository.Read(dependencyVulnID)
237+
dependencyVuln, err := controller.dependencyVulnRepository.Read(dependencyVulnID)
238238
if err != nil {
239239
return echo.NewHTTPError(404, "could not find dependencyVuln")
240240
}
@@ -246,7 +246,28 @@ func (c dependencyVulnHTTPController) Read(ctx core.Context) error {
246246
return ctx.JSON(200, convertToDetailedDTO(dependencyVuln))
247247
}
248248

249-
func (c dependencyVulnHTTPController) CreateEvent(ctx core.Context) error {
249+
func (controller dependencyVulnHTTPController) Hints(ctx core.Context) error {
250+
//if enabled in org settings we also want to send hints
251+
org := core.GetOrg(ctx)
252+
253+
dependencyVulnID, _, err := core.GetVulnID(ctx)
254+
if err != nil {
255+
return echo.NewHTTPError(400, "invalid dependencyVuln id")
256+
}
257+
258+
dependencyVuln, err := controller.dependencyVulnRepository.Read(dependencyVulnID)
259+
if err != nil {
260+
return echo.NewHTTPError(404, "could not find dependencyVuln")
261+
}
262+
263+
hints, err := controller.dependencyVulnRepository.GetHintsInOrganizationForVuln(nil, org.ID, *dependencyVuln.ComponentPurl, *dependencyVuln.CVEID)
264+
if err != nil {
265+
return err
266+
}
267+
return ctx.JSON(200, hints)
268+
}
269+
270+
func (controller dependencyVulnHTTPController) CreateEvent(ctx core.Context) error {
250271
asset := core.GetAsset(ctx)
251272
assetVersion := core.GetAssetVersion(ctx)
252273
thirdPartyIntegration := core.GetThirdPartyIntegration(ctx)
@@ -255,7 +276,7 @@ func (c dependencyVulnHTTPController) CreateEvent(ctx core.Context) error {
255276
return echo.NewHTTPError(400, "invalid dependencyVuln id")
256277
}
257278

258-
dependencyVuln, err := c.dependencyVulnRepository.Read(dependencyVulnID)
279+
dependencyVuln, err := controller.dependencyVulnRepository.Read(dependencyVulnID)
259280
if err != nil {
260281
return echo.NewHTTPError(404, "could not find dependencyVuln")
261282
}
@@ -275,7 +296,7 @@ func (c dependencyVulnHTTPController) CreateEvent(ctx core.Context) error {
275296
justification := status.Justification
276297
mechanicalJustification := status.MechanicalJustification
277298

278-
ev, err := c.dependencyVulnService.UpdateDependencyVulnState(nil, asset.ID, userID, &dependencyVuln, statusType, justification, mechanicalJustification, assetVersion.Name)
299+
ev, err := controller.dependencyVulnService.UpdateDependencyVulnState(nil, asset.ID, userID, &dependencyVuln, statusType, justification, mechanicalJustification, assetVersion.Name)
279300
if err != nil {
280301
return err
281302
}

internal/database/models/org_model.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ type Org struct {
2323

2424
JiraIntegrations []JiraIntegration `json:"jiraIntegrations" gorm:"foreignKey:OrgID;"`
2525

26-
Webhooks []WebhookIntegration `json:"webhooks" gorm:"foreignKey:OrgID;"`
26+
SharesVulnInformation bool `json:"sharesVulnInformation" gorm:"default:false"`
27+
Webhooks []WebhookIntegration `json:"webhooks" gorm:"foreignKey:OrgID;"`
2728

2829
IsPublic bool `json:"isPublic" gorm:"default:false;"`
2930

internal/database/repositories/dependency_vuln_repository.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package repositories
22

33
import (
4+
"fmt"
5+
"log/slog"
6+
47
"github.com/google/uuid"
8+
"github.com/l3montree-dev/devguard/internal/common"
59
"github.com/l3montree-dev/devguard/internal/core"
610
"github.com/l3montree-dev/devguard/internal/utils"
711

@@ -282,3 +286,41 @@ func (repository *dependencyVulnRepository) FindByTicketID(tx core.DB, ticketID
282286
}
283287
return vuln, nil
284288
}
289+
290+
func (repository *dependencyVulnRepository) GetHintsInOrganizationForVuln(tx core.DB, orgID uuid.UUID, pURL string, cveID string) (common.DependencyVulnHints, error) {
291+
type stateCount struct {
292+
State string `json:"state"`
293+
Count int `json:"count"`
294+
}
295+
var hints common.DependencyVulnHints
296+
stateCounts := make([]stateCount, 0, 7)
297+
298+
err := repository.GetDB(tx).Debug().Raw(`SELECT d.state as "state", COUNT(d.state) as "count" FROM dependency_vulns d WHERE asset_id IN (
299+
SELECT id from assets WHERE project_id IN (
300+
SELECT id from projects WHERE organization_id = ?
301+
)
302+
) AND d.cve_id = ? AND d.component_purl = ? GROUP BY d.state`, orgID, cveID, pURL).Scan(&stateCounts).Error
303+
if err != nil {
304+
return hints, err
305+
}
306+
// convert information from query to hints struct
307+
for _, state := range stateCounts {
308+
//maybe use VulnStates for this, needs conversion then
309+
switch state.State {
310+
case "open":
311+
hints.AmountOpen += state.Count
312+
case "fixed":
313+
hints.AmountFixed += state.Count
314+
case "accepted":
315+
hints.AmountAccepted += state.Count
316+
case "falsePositive":
317+
hints.AmountFalsePositive += state.Count
318+
case "markedForTransfer":
319+
hints.AmountMarkedForTransfer += state.Count
320+
default:
321+
slog.Error("invalid state", "state", state.State) //debug for now, can be removed later
322+
return hints, fmt.Errorf("invalid state")
323+
}
324+
}
325+
return hints, nil
326+
}

0 commit comments

Comments
 (0)