Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 4 additions & 3 deletions normalize/sbom_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/google/uuid"
"github.com/l3montree-dev/devguard/monitoring"
"github.com/package-url/packageurl-go"
)

Expand Down Expand Up @@ -929,11 +930,11 @@ func (g *SBOMGraph) FindAllComponentOnlyPathsToPURL(purl string, limit int) []Pa
// if not, just discard this path, as it does not belong to the scoped artifact
parentArtifact := reverseEdges[parentID]
if len(parentArtifact) > 1 {
panic("more than one parent, makes no sense")
slog.Warn("Info source has multiple parents, which should not happen in a well-formed graph. This may lead to incorrect path results.", "infoSourceID", parentID, "parentArtifacts", parentArtifact)
}
Comment thread
timbastin marked this conversation as resolved.

if parentArtifact[0] != g.ScopeID {
// this info source does not belong to the scoped artifact, discard path
if !slices.Contains(parentArtifact, g.ScopeID) {
// parent artifact is not the one we scoped to, so skip this path
continue
}
}
Expand Down
37 changes: 37 additions & 0 deletions normalize/sbom_graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,43 @@ func TestFindAllComponentOnlyPathsToPURL_ScopeIsolation(t *testing.T) {
assert.NotEqual(t, artifact1ID, artifact2ID)
assert.NotEqual(t, infoSource1, infoSource2)
})

t.Run("info source with two parent artifacts still returns correct scoped paths", func(t *testing.T) {
// Simulate a malformed (but real-world) graph where the same info source
// node is reachable from two different artifact nodes. The warning branch
// (len(parentArtifact) > 1) must still produce correct results: scoping
// to either artifact must find the path, not drop it.
g := NewSBOMGraph()

artifact1ID := g.AddArtifact("app1")
artifact2ID := g.AddArtifact("app2")

// Create an info source under artifact1.
infoSourceID := g.AddInfoSource(artifact1ID, "shared/sbom.json", InfoSourceSBOM)

// Manually wire artifact2 → the same info source, creating the
// "multiple parents" condition that triggers the warning.
g.AddEdge(artifact2ID, infoSourceID)

lodash := cdx.Component{BOMRef: "pkg:npm/lodash@4.17.20", PackageURL: "pkg:npm/lodash@4.17.20"}
lodashID := g.AddComponent(lodash)
g.AddEdge(infoSourceID, lodashID)

// Scoped to artifact1: path must still be returned despite the warning.
err := g.ScopeToArtifact("app1")
assert.NoError(t, err)
paths1 := g.FindAllComponentOnlyPathsToPURL("pkg:npm/lodash@4.17.20", 0)
assert.Len(t, paths1, 1, "scoped to app1 should return the path even with multiple info-source parents")
assert.Equal(t, Path([]string{"pkg:npm/lodash@4.17.20"}), paths1[0])

// Scoped to artifact2: same expectation.
g.ClearScope()
err = g.ScopeToArtifact("app2")
assert.NoError(t, err)
paths2 := g.FindAllComponentOnlyPathsToPURL("pkg:npm/lodash@4.17.20", 0)
assert.Len(t, paths2, 1, "scoped to app2 should return the path even with multiple info-source parents")
assert.Equal(t, Path([]string{"pkg:npm/lodash@4.17.20"}), paths2[0])
})
}

func TestVulnerabilities(t *testing.T) {
Expand Down
Loading