diff --git a/sbom/builders.go b/sbom/builders.go index 7d22ccb..4b1f317 100644 --- a/sbom/builders.go +++ b/sbom/builders.go @@ -58,7 +58,7 @@ func Go(version, path string) (cdx.Component, error) { if err != nil { return cdx.Component{}, err } - deps := Modules(info) + deps := Modules(info, version) return cdx.Component{ Type: cdx.ComponentTypeApplication, Name: filepath.Base(path), Version: version, PackageURL: "pkg:golang/" + info.Main.Path + "@" + version, Hashes: sha(sum), Components: &deps, @@ -68,15 +68,23 @@ func Go(version, path string) (cdx.Component, error) { // Modules is what a Go build info says the binary is made of: the standard library of its // toolchain, and each module - the replacement where there is one, since that is what was // compiled - with the go.sum hash it was checked against. -func Modules(info *debug.BuildInfo) []cdx.Component { +// +// A module of the binary's own tree - replaced by a directory of it (Alloy's `=> ../syntax`), or a +// workspace's (Grafana's apps) - is stamped (devel), which no scanner matches: it was compiled +// from the tree at the binary's version, so it is that, under the path it is published by. +func Modules(info *debug.BuildInfo, version string) []cdx.Component { deps := []cdx.Component{{ Type: cdx.ComponentTypeLibrary, Name: "stdlib", Version: strings.TrimPrefix(info.GoVersion, "go"), PackageURL: "pkg:golang/stdlib@" + strings.TrimPrefix(info.GoVersion, "go"), }} for _, d := range info.Deps { + published := d.Path if d.Replace != nil { d = d.Replace } + if d.Version == "(devel)" { + d = &debug.Module{Path: published, Version: version} + } c := cdx.Component{Type: cdx.ComponentTypeLibrary, Name: d.Path, Version: d.Version, PackageURL: "pkg:golang/" + d.Path + "@" + d.Version} if d.Sum != "" { c.Properties = &[]cdx.Property{{Name: "go:sum", Value: d.Sum}} diff --git a/sbom/sbom_test.go b/sbom/sbom_test.go index 01fdf90..3ce3154 100644 --- a/sbom/sbom_test.go +++ b/sbom/sbom_test.go @@ -260,14 +260,17 @@ func TestDpkgListingIsTheDistributions(t *testing.T) { } // A module replaced is the replacement, which is what was compiled, with the go.sum hash it was -// checked against; the standard library is the toolchain's. +// checked against; the standard library is the toolchain's; and a module of the binary's own tree, +// which Go stamps (devel), is the binary's version under the path it is published by. func TestModulesAreWhatWasCompiled(t *testing.T) { info := &debug.BuildInfo{GoVersion: "go1.27.2", Deps: []*debug.Module{ {Path: "golang.org/x/net", Version: "v0.60.0", Sum: "h1:abc="}, {Path: "github.com/a/b", Version: "v1.0.0", Replace: &debug.Module{Path: "github.com/fork/b", Version: "v1.0.1", Sum: "h1:def="}}, + {Path: "github.com/grafana/alloy/syntax", Version: "v0.1.0", Replace: &debug.Module{Path: "../syntax", Version: "(devel)"}}, + {Path: "github.com/grafana/grafana/apps/playlist", Version: "(devel)"}, }} got := map[string]string{} - for _, c := range sbom.Modules(info) { + for _, c := range sbom.Modules(info, "v13.2.4") { sum := "" if c.Properties != nil { sum = (*c.Properties)[0].Value @@ -276,6 +279,7 @@ func TestModulesAreWhatWasCompiled(t *testing.T) { } assert.Equal(t, map[string]string{ "pkg:golang/stdlib@1.27.2": "", "pkg:golang/golang.org/x/net@v0.60.0": "h1:abc=", "pkg:golang/github.com/fork/b@v1.0.1": "h1:def=", + "pkg:golang/github.com/grafana/alloy/syntax@v13.2.4": "", "pkg:golang/github.com/grafana/grafana/apps/playlist@v13.2.4": "", }, got) }