diff --git a/README.md b/README.md index ffd091b..4273aef 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ sbom.Encode(os.Stdout, s, sbom.FormatSPDXJSON) ## Relationships -Both parsers normalise dependency edges to `sbom.RelDependsOn` and SPDX's document-to-root link to `sbom.RelDescribes`. `ClassifyScope` walks that graph to tag each package as a direct or transitive dependency of the document's root: +Both parsers normalise dependency edges to `sbom.RelDependsOn`, SPDX `DEPENDENCY_OF` edges included with their direction reversed, and SPDX's document-to-root link to `sbom.RelDescribes`. `ClassifyScope` walks that graph to tag each package as a direct or transitive dependency of the document's root: ```go doc, _ := sbom.Parse(data) @@ -91,6 +91,14 @@ for _, p := range doc.Packages { `ClassifyScope` returns `nil` when the document has no dependency graph at all, so callers can hide a scope filter rather than showing every package as unclassified. +When the document declares its root, through an SPDX `DESCRIBES` edge or the CycloneDX `metadata.component` bom-ref (kept as `Document.Component.ID`), and that root has dependency edges, it is the only root. Otherwise roots are inferred as the nodes that have dependencies but no dependents. That is the project for lock files that list it, such as `package-lock.json`, `Cargo.lock` and `uv.lock`. Lock files such as `poetry.lock` list only dependencies, so the inferred roots would be the top-level dependencies. For packages syft records as coming from `poetry.lock`, `pdm.lock`, `pnpm-lock.yaml` or a yarn v1 `yarn.lock` (the `syft:location:0:path` property in CycloneDX, `PackageSourceInfo` in SPDX), those are classified as direct instead, as is any package from such a lock file that no edge names, such as a dependency without dependencies of its own, as long as the lock file has edges at all. Other generators' lock-file SBOMs that don't link the project may still have top-level dependencies inferred as roots. + +`Roots` returns the root IDs and whether the document declared all of them, so a caller can tell the project apart from an inferred root: + +```go +ids, declared := doc.Roots() +``` + ## Ancestors `Package.Ancestors` records the components a package was derived from, such as the upstream project of a fork. CycloneDX JSON and XML read and write these as `pedigree.ancestors`: diff --git a/cyclonedx.go b/cyclonedx.go index ecac6ba..da090c8 100644 --- a/cyclonedx.go +++ b/cyclonedx.go @@ -294,6 +294,7 @@ func normalizeCycloneDX(bom *cdxBOM) *SBOM { if m.Component != nil { s.Document.Name = m.Component.Name s.Document.Component = Component{ + ID: m.Component.BOMRef, Type: m.Component.Type, Name: m.Component.Name, Version: m.Component.Version, diff --git a/encode.go b/encode.go index 82e67c4..a4534ef 100644 --- a/encode.go +++ b/encode.go @@ -67,7 +67,8 @@ func buildCycloneDX(s *SBOM) *cdxBOM { } if c := s.Document.Component; c.Name != "" { bom.Metadata.Component = &cdxComponent{ - Type: firstNonEmpty(c.Type, "application"), Name: c.Name, Version: c.Version, + BOMRef: c.ID, + Type: firstNonEmpty(c.Type, "application"), Name: c.Name, Version: c.Version, Licenses: componentLicensesToCDX(c), } } diff --git a/sbom.go b/sbom.go index 2d48762..a2d918a 100644 --- a/sbom.go +++ b/sbom.go @@ -16,6 +16,7 @@ import ( "encoding/json" "errors" "fmt" + "maps" "slices" "strings" ) @@ -61,8 +62,11 @@ type Creator struct { } // Component is the root subject the SBOM describes (CycloneDX -// metadata.component / SPDX root package). +// metadata.component / SPDX root package). ID is its CycloneDX bom-ref, the +// identifier dependency edges from the root use; it is empty when the +// document gives none. type Component struct { + ID string Type string Name string Version string @@ -117,6 +121,9 @@ type Package struct { Homepage string DownloadLocation string Filename string + // SourceInfo is SPDX's free-text note on where the package information + // came from (PackageSourceInfo). + SourceInfo string LicenseConcluded string LicenseDeclared string Copyright string @@ -162,13 +169,16 @@ func (p *Package) CPE() string { } // Relationship type values. SPDX has ~40 of these; we name the two the -// parsers emit and ClassifyScope reads. CycloneDX dependency edges are -// normalised to RelDependsOn. +// parsers emit and ClassifyScope reads. CycloneDX dependency edges and +// SPDX DEPENDENCY_OF edges are normalised to RelDependsOn. const ( RelDependsOn = "DEPENDS_ON" RelDescribes = "DESCRIBES" ) +// spdxDependencyOf is the SPDX inverse of DEPENDS_ON, which syft emits. +const spdxDependencyOf = "DEPENDENCY_OF" + // Scope values returned by ClassifyScope. const ( ScopeDirect = "direct" @@ -252,39 +262,38 @@ func (s *SBOM) FilterProperties(keep func(name string) bool) { } // ClassifyScope derives direct-vs-transitive scope for each package from -// the relationship graph. Roots are nodes that originate DEPENDS_ON edges -// but are never themselves a DEPENDS_ON target, plus anything a DESCRIBES -// edge points at (SPDX's document-to-root-package link). A package is -// ScopeDirect if a root depends on it, ScopeTransitive if only a non-root -// does, and absent from the map if the graph doesn't mention it at all. +// the relationship graph. A package is ScopeDirect if a root depends on it, +// ScopeTransitive if only a non-root does, and absent from the map if the +// graph doesn't mention it at all. +// +// When the document declares its root, as the target of a DESCRIBES edge +// (SPDX's document-to-root-package link) or as Document.Component.ID (the +// CycloneDX metadata.component bom-ref), and that root has DEPENDS_ON edges, +// the declared roots are the only roots. Otherwise roots are inferred: nodes +// that originate DEPENDS_ON edges but are never a DEPENDS_ON target, plus any +// declared root. An inferred root is not necessarily the project: for a lock +// file that doesn't list the project itself the top-level dependencies are +// inferred as roots, except for packages syft records as coming from such a +// lock file (poetry.lock, pdm.lock, pnpm-lock.yaml and yarn v1's yarn.lock): +// those are ScopeDirect themselves, and what they depend on is transitive. +// A package from such a lock file that no edge names is ScopeDirect too, +// when other packages of the lock file have edges. +// // Returns nil for a flat-list SBOM with no dependency graph so callers can // tell "no graph" apart from "graph present but this package is a root". func (s *SBOM) ClassifyScope() map[string]string { - targets := map[string]bool{} - sources := map[string]bool{} - roots := map[string]bool{} - for _, r := range s.Relationships { - switch r.Type { - case RelDependsOn: - sources[r.SourceID] = true - targets[r.TargetID] = true - case RelDescribes: - roots[r.TargetID] = true - } - } - if len(sources) == 0 && len(roots) == 0 { + roots, top, _ := s.scopeRoots() + if roots == nil { // No DEPENDS_ON or DESCRIBES edges: a flat-list SBOM. nil (as // distinct from empty) lets callers hide scope entirely. return nil } - for id := range sources { - if !targets[id] { - roots[id] = true - } - } out := map[string]string{} - if len(roots) == 0 { - // Edges exist but form a cycle with no DESCRIBES root: nothing + for id := range top { + out[id] = ScopeDirect + } + if len(roots) == 0 && len(top) == 0 { + // Edges exist but form a cycle with no declared root: nothing // is classifiable, but the graph is present, so return empty // rather than nil. return out @@ -302,6 +311,75 @@ func (s *SBOM) ClassifyScope() map[string]string { return out } +// Roots returns the IDs ClassifyScope treats as roots, and whether all of +// them are declared by the document rather than inferred from the graph. Declared +// roots are the project; inferred ones may be top-level dependencies of a +// lock file that leaves the project out. It returns nil for a flat-list SBOM. +func (s *SBOM) Roots() (ids []string, declared bool) { + roots, _, declared := s.scopeRoots() + if roots == nil { + return nil, false + } + ids = make([]string, 0, len(roots)) + for id := range roots { + ids = append(ids, id) + } + slices.Sort(ids) + return ids, declared +} + +// scopeRoots returns the root set for ClassifyScope, nil when there is no +// graph, the inferred roots that are top-level dependencies instead (see +// topLevelDependencies), and whether every root is declared. +func (s *SBOM) scopeRoots() (roots, top map[string]bool, declared bool) { + targets := map[string]bool{} + sources := map[string]bool{} + declaredRoots := map[string]bool{} + for _, r := range s.Relationships { + switch r.Type { + case RelDependsOn: + sources[r.SourceID] = true + targets[r.TargetID] = true + case RelDescribes: + declaredRoots[r.TargetID] = true + } + } + if len(sources) == 0 && len(declaredRoots) == 0 { + return nil, nil, false + } + if id := s.Document.Component.ID; id != "" { + declaredRoots[id] = true + } + // A declared root with edges of its own names the project's direct + // dependencies, so nothing needs inferring. + linked := map[string]bool{} + for id := range declaredRoots { + if sources[id] { + linked[id] = true + } + } + if len(linked) > 0 { + return linked, nil, true + } + inferred := map[string]bool{} + for id := range sources { + if !targets[id] { + inferred[id] = true + } + } + edged := maps.Clone(sources) + maps.Copy(edged, targets) + top = s.topLevelDependencies(inferred, edged) + roots = maps.Clone(declaredRoots) + for id := range inferred { + if !top[id] { + roots[id] = true + } + } + declared = len(roots) > 0 && len(roots) == len(declaredRoots) + return roots, top, declared +} + func (s *SBOM) addPackage(p Package) { if s.pkgIndex == nil { s.pkgIndex = map[packageKey]int{} diff --git a/scope_lock.go b/scope_lock.go new file mode 100644 index 0000000..d8d40b0 --- /dev/null +++ b/scope_lock.go @@ -0,0 +1,105 @@ +package sbom + +import ( + "path" + "strings" +) + +// These lock files list only the project's dependencies, not the project +// itself, so a package from one that nothing depends on is a direct +// dependency rather than the project. npm's package-lock.json, Cargo.lock, +// uv.lock and yarn berry's yarn.lock do list the project, and are left to +// root inference. +var projectlessLockFiles = map[string]bool{ + "poetry.lock": true, + "pdm.lock": true, + "pnpm-lock.yaml": true, + "yarn.lock": true, // yarn v1; see yarnBerryWorkspaces +} + +const ( + // syftLocationPath is the CycloneDX property syft records the file a + // package was read from in. + syftLocationPath = "syft:location:0:path" + // yarnBerryWorkspaceVersion is the version yarn berry gives the + // workspace entries it records for the project. + yarnBerryWorkspaceVersion = "0.0.0-use.local" +) + +// lockFile returns the path of the file syft read p from: the location +// property in CycloneDX, or the path ending SPDX's PackageSourceInfo, as in +// "acquired package info from installed python package manifest file: +// /poetry.lock". +func (p *Package) lockFile() string { + if loc := p.property(syftLocationPath); loc != "" { + return loc + } + if i := strings.LastIndex(p.SourceInfo, ": /"); i >= 0 { + return p.SourceInfo[i+2:] + } + return "" +} + +// topLevelDependencies returns the IDs of packages from a lock file that +// leaves the project out which nothing depends on, so are dependencies of +// the project rather than the project itself: the inferred roots among them, +// and those without any edge, such as a dependency that has none of its own. +// A package without edges counts only when other packages of its lock file +// have edges, so a lock file the graph leaves out stays unclassified. +// edged holds the IDs that are the source or target of a DEPENDS_ON edge. +func (s *SBOM) topLevelDependencies(inferred, edged map[string]bool) map[string]bool { + var berryLocks map[string]bool + lockHasEdges := map[string]bool{} + for i := range s.Packages { + if p := &s.Packages[i]; edged[p.ID] { + lockHasEdges[p.lockFile()] = true + } + } + var top map[string]bool + for i := range s.Packages { + p := &s.Packages[i] + lock := p.lockFile() + if !inferred[p.ID] && (edged[p.ID] || !lockHasEdges[lock]) { + continue + } + name := path.Base(lock) + if lock == "" || !projectlessLockFiles[name] { + continue + } + if name == "yarn.lock" { + if berryLocks == nil { + berryLocks = s.yarnBerryWorkspaces() + } + if berryLocks[lock] { + continue + } + } + if top == nil { + top = map[string]bool{} + } + top[p.ID] = true + } + return top +} + +// yarnBerryWorkspaces returns the yarn.lock paths that record a workspace +// entry, which yarn v1 lock files don't have. +func (s *SBOM) yarnBerryWorkspaces() map[string]bool { + locks := map[string]bool{} + for i := range s.Packages { + p := &s.Packages[i] + if lock := p.lockFile(); p.Version == yarnBerryWorkspaceVersion && path.Base(lock) == "yarn.lock" { + locks[lock] = true + } + } + return locks +} + +func (p *Package) property(name string) string { + for _, prop := range p.Properties { + if prop.Name == name { + return prop.Value + } + } + return "" +} diff --git a/scope_test.go b/scope_test.go index 4de5884..676c4e7 100644 --- a/scope_test.go +++ b/scope_test.go @@ -1,6 +1,15 @@ package sbom -import "testing" +import ( + "bytes" + "fmt" + "maps" + "os" + "path/filepath" + "slices" + "strings" + "testing" +) func TestClassifyScope(t *testing.T) { t.Run("cyclonedx graph", func(t *testing.T) { @@ -102,3 +111,239 @@ func TestClassifyScope(t *testing.T) { } }) } + +func TestClassifyScopeDeclaredRoot(t *testing.T) { + t.Run("cyclonedx component with edges is the only root", func(t *testing.T) { + // The project links requests; the stray lock entry orphan -> x is + // not a root once the project is declared. + s := &SBOM{ + Document: Document{Component: Component{ID: "project"}}, + Relationships: []Relationship{ + {SourceID: "project", TargetID: "requests", Type: RelDependsOn}, + {SourceID: "requests", TargetID: "urllib3", Type: RelDependsOn}, + {SourceID: "orphan", TargetID: "x", Type: RelDependsOn}, + }, + } + got := s.ClassifyScope() + want := map[string]string{"requests": ScopeDirect, "urllib3": ScopeTransitive, "x": ScopeTransitive} + if len(got) != len(want) { + t.Fatalf("got %v, want %v", got, want) + } + for id, sc := range want { + if got[id] != sc { + t.Errorf("%s = %q, want %q", id, got[id], sc) + } + } + if ids, declared := s.Roots(); !declared || len(ids) != 1 || ids[0] != "project" { + t.Errorf("Roots() = %v, %v; want [project], true", ids, declared) + } + }) + t.Run("spdx DESCRIBES target with edges is the only root", func(t *testing.T) { + s := &SBOM{Relationships: []Relationship{ + {SourceID: "SPDXRef-DOCUMENT", TargetID: "root", Type: RelDescribes}, + {SourceID: "root", TargetID: "a", Type: RelDependsOn}, + {SourceID: "other", TargetID: "b", Type: RelDependsOn}, + }} + got := s.ClassifyScope() + if got["a"] != ScopeDirect || got["b"] != ScopeTransitive { + t.Errorf("got %v", got) + } + if _, declared := s.Roots(); !declared { + t.Error("Roots() declared = false, want true") + } + }) + t.Run("declared root without edges falls back to inference", func(t *testing.T) { + // syft's CycloneDX for a poetry.lock: metadata.component is the + // scanned directory with no edges, so requests is inferred as a root. + s := &SBOM{ + Document: Document{Component: Component{ID: "dir"}}, + Relationships: []Relationship{ + {SourceID: "requests", TargetID: "urllib3", Type: RelDependsOn}, + }, + } + got := s.ClassifyScope() + if len(got) != 1 || got["urllib3"] != ScopeDirect { + t.Errorf("got %v, want urllib3 direct", got) + } + ids, declared := s.Roots() + if declared { + t.Error("Roots() declared = true, want false") + } + if len(ids) != 2 || ids[0] != "dir" || ids[1] != "requests" { + t.Errorf("Roots() = %v, want [dir requests]", ids) + } + }) + t.Run("spdx DESCRIBES target without edges is a declared root", func(t *testing.T) { + doc, err := Parse([]byte(`{ + "spdxVersion": "SPDX-2.3", + "SPDXID": "SPDXRef-DOCUMENT", + "packages": [{"SPDXID": "SPDXRef-app", "name": "app"}], + "relationships": [{"spdxElementId": "SPDXRef-DOCUMENT", "relationshipType": "DESCRIBES", "relatedSpdxElement": "SPDXRef-app"}] + }`)) + if err != nil { + t.Fatal(err) + } + if ids, declared := doc.Roots(); !declared || !slices.Equal(ids, []string{"SPDXRef-app"}) { + t.Errorf("Roots() = %v, %v; want [SPDXRef-app], true", ids, declared) + } + }) + t.Run("declared root without edges and inferred roots is not declared", func(t *testing.T) { + s := &SBOM{Relationships: []Relationship{ + {SourceID: "SPDXRef-DOCUMENT", TargetID: "app", Type: RelDescribes}, + {SourceID: "lib", TargetID: "dep", Type: RelDependsOn}, + }} + if ids, declared := s.Roots(); declared || !slices.Equal(ids, []string{"app", "lib"}) { + t.Errorf("Roots() = %v, %v; want [app lib], false", ids, declared) + } + }) + t.Run("no graph", func(t *testing.T) { + s := &SBOM{Document: Document{Component: Component{ID: "project"}}} + if got := s.ClassifyScope(); got != nil { + t.Errorf("got %v, want nil", got) + } + if ids, declared := s.Roots(); ids != nil || declared { + t.Errorf("Roots() = %v, %v; want nil, false", ids, declared) + } + }) +} + +func TestClassifyScopeCycloneDXMetadataComponent(t *testing.T) { + data := []byte(`{ + "bomFormat": "CycloneDX", "specVersion": "1.6", + "metadata": {"component": {"bom-ref": "app", "type": "application", "name": "app"}}, + "components": [ + {"bom-ref": "requests", "type": "library", "name": "requests", "version": "2.31.0"}, + {"bom-ref": "urllib3", "type": "library", "name": "urllib3", "version": "2.1.0"}, + {"bom-ref": "pytest", "type": "library", "name": "pytest", "version": "8.0.0"}, + {"bom-ref": "pluggy", "type": "library", "name": "pluggy", "version": "1.4.0"} + ], + "dependencies": [ + {"ref": "app", "dependsOn": ["requests"]}, + {"ref": "requests", "dependsOn": ["urllib3"]}, + {"ref": "pytest", "dependsOn": ["pluggy"]} + ] +}`) + doc, err := Parse(data) + if err != nil { + t.Fatal(err) + } + if doc.Document.Component.ID != "app" { + t.Fatalf("Component.ID = %q, want app", doc.Document.Component.ID) + } + got := doc.ClassifyScope() + want := map[string]string{"requests": ScopeDirect, "urllib3": ScopeTransitive, "pluggy": ScopeTransitive} + if len(got) != len(want) { + t.Fatalf("got %v, want %v", got, want) + } + for id, sc := range want { + if got[id] != sc { + t.Errorf("%s = %q, want %q", id, got[id], sc) + } + } + + var buf bytes.Buffer + if err := Encode(&buf, doc, FormatCycloneDXJSON); err != nil { + t.Fatal(err) + } + again, err := Parse(buf.Bytes()) + if err != nil { + t.Fatal(err) + } + if again.Document.Component.ID != "app" { + t.Errorf("encoded Component.ID = %q, want app", again.Document.Component.ID) + } +} + +// syft's CycloneDX output for one small project per lock format, generated +// with syft 1.55.0. Lock files that list the project (package-lock.json, +// Cargo.lock, uv.lock, yarn berry) give it a node of its own; the others +// list only dependencies. +func TestClassifyScopeSyftLockFiles(t *testing.T) { + // Lock files that name the project add it as an inferred root; for the + // others only the declared directory remains, so the roots are declared. + tests := []struct { + file string + want map[string]string + roots []string + declared bool + }{ + {"npm", map[string]string{"express": ScopeDirect, "accepts": ScopeTransitive}, []string{"", "app"}, false}, + {"cargo", map[string]string{"serde": ScopeDirect, "serde_derive": ScopeTransitive}, []string{"", "app"}, false}, + {"uv", map[string]string{"requests": ScopeDirect, "urllib3": ScopeTransitive}, []string{"", "app"}, false}, + {"berry", map[string]string{"express": ScopeDirect, "accepts": ScopeTransitive}, []string{"", "app"}, false}, + {"poetry", map[string]string{"requests": ScopeDirect, "urllib3": ScopeTransitive}, []string{""}, true}, + {"pdm", map[string]string{"requests": ScopeDirect, "urllib3": ScopeTransitive}, []string{""}, true}, + {"pnpm9", map[string]string{"express": ScopeDirect, "accepts": ScopeTransitive}, []string{""}, true}, + {"yarn", map[string]string{"express": ScopeDirect, "accepts": ScopeTransitive}, []string{""}, true}, + // six has no dependencies and nothing depends on it, so no edge + // names it; from a lock file without the project it is direct. + {"poetry-leaf", map[string]string{"requests": ScopeDirect, "idna": ScopeTransitive, "six": ScopeDirect}, []string{""}, true}, + } + for _, tt := range tests { + for _, format := range []string{"cyclonedx/syft-lock/%s.cdx.json", "spdx/syft-lock/%s.spdx.json"} { + t.Run(fmt.Sprintf(format, tt.file), func(t *testing.T) { + testSyftLockScope(t, filepath.Join("testdata", fmt.Sprintf(format, tt.file)), tt.want, tt.roots, tt.declared) + }) + } + } +} + +func testSyftLockScope(t *testing.T, file string, want map[string]string, wantRoots []string, wantDeclared bool) { + t.Helper() + data, err := os.ReadFile(file) + if err != nil { + t.Fatal(err) + } + doc, err := Parse(data) + if err != nil { + t.Fatal(err) + } + names := map[string]string{} + for _, p := range doc.Packages { + // The scanned directory is a package in SPDX; in CycloneDX it + // is metadata.component, without a package entry. + if !strings.HasPrefix(p.ID, "SPDXRef-DocumentRoot-") { + names[p.ID] = p.Name + } + } + got := map[string]string{} + for id, sc := range doc.ClassifyScope() { + got[names[id]] = sc + } + if !maps.Equal(got, want) { + t.Errorf("scope = %v, want %v", got, want) + } + // The scanned directory, which syft declares as the root without + // edges, is a root too and shows as "". + ids, declared := doc.Roots() + var roots []string + for _, id := range ids { + roots = append(roots, names[id]) + } + slices.Sort(roots) + if declared != wantDeclared || !slices.Equal(roots, wantRoots) { + t.Errorf("Roots() = %v, %v; want %v, %v", roots, declared, wantRoots, wantDeclared) + } +} + +// TestClassifyScopeLockFileWithoutEdges checks that packages of a lock file +// the graph has no edges for stay unclassified, even though no edge names +// them, while another lock file has edges. +func TestClassifyScopeLockFileWithoutEdges(t *testing.T) { + from := func(id, name, lock string) Package { + return Package{ID: id, Name: name, Properties: []Property{{Name: syftLocationPath, Value: lock}}} + } + s := &SBOM{ + Packages: []Package{ + from("app", "app", "/web/package-lock.json"), + from("express", "express", "/web/package-lock.json"), + from("requests", "requests", "/py/poetry.lock"), + from("idna", "idna", "/py/poetry.lock"), + }, + Relationships: []Relationship{{SourceID: "app", TargetID: "express", Type: RelDependsOn}}, + } + want := map[string]string{"express": ScopeDirect} + if got := s.ClassifyScope(); !maps.Equal(got, want) { + t.Errorf("scope = %v, want %v", got, want) + } +} diff --git a/spdx.go b/spdx.go index 8de7f92..05e60b3 100644 --- a/spdx.go +++ b/spdx.go @@ -49,6 +49,7 @@ type spdxPackage struct { Supplier string `json:"supplier,omitempty"` Originator string `json:"originator,omitempty"` PrimaryPackagePurpose string `json:"primaryPackagePurpose,omitempty"` + SourceInfo string `json:"sourceInfo,omitempty"` Checksums []spdxChecksum `json:"checksums,omitempty"` ExternalRefs []spdxExtRef `json:"externalRefs,omitempty"` } @@ -118,6 +119,7 @@ func normalizeSPDX(doc *spdxDoc) *SBOM { Homepage: sp.Homepage, DownloadLocation: sp.DownloadLocation, Filename: sp.PackageFileName, + SourceInfo: sp.SourceInfo, LicenseConcluded: sp.LicenseConcluded, LicenseDeclared: sp.LicenseDeclared, Copyright: sp.CopyrightText, @@ -148,12 +150,17 @@ func normalizeSPDX(doc *spdxDoc) *SBOM { for i := range doc.Relationships { r := &doc.Relationships[i] + source, target, typ := r.SPDXElementID, r.RelatedSPDXElement, r.RelationshipType + if typ == spdxDependencyOf { + // "B DEPENDENCY_OF A" is "A DEPENDS_ON B". + source, target, typ = target, source, RelDependsOn + } s.Relationships = append(s.Relationships, Relationship{ - SourceID: r.SPDXElementID, - Source: elements[r.SPDXElementID], - TargetID: r.RelatedSPDXElement, - Target: elements[r.RelatedSPDXElement], - Type: r.RelationshipType, + SourceID: source, + Source: elements[source], + TargetID: target, + Target: elements[target], + Type: typ, }) } diff --git a/spdx_tagvalue.go b/spdx_tagvalue.go index b848338..cb52718 100644 --- a/spdx_tagvalue.go +++ b/spdx_tagvalue.go @@ -173,6 +173,8 @@ func (p *spdxTagParser) packageField(tag, value string) error { p.pkg.CopyrightText = value case "PackageDescription": p.pkg.Description = value + case "PackageSourceInfo": + p.pkg.SourceInfo = value case "PrimaryPackagePurpose": p.pkg.PrimaryPackagePurpose = value case "PackageChecksum": diff --git a/spdx_test.go b/spdx_test.go index 0527d45..8bc237b 100644 --- a/spdx_test.go +++ b/spdx_test.go @@ -169,3 +169,32 @@ func TestSPDXEnvelopeDepthLimit(t *testing.T) { t.Fatalf("Parse = %v, want ErrUnrecognized", err) } } + +func TestSPDXDependencyOfNormalised(t *testing.T) { + inputs := map[string]string{ + "json": `{"spdxVersion": "SPDX-2.3", "SPDXID": "SPDXRef-DOCUMENT", + "packages": [ + {"SPDXID": "SPDXRef-a", "name": "a", "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock"}, + {"SPDXID": "SPDXRef-b", "name": "b"}], + "relationships": [{"spdxElementId": "SPDXRef-b", "relationshipType": "DEPENDENCY_OF", "relatedSpdxElement": "SPDXRef-a"}]}`, + "tag-value": "SPDXVersion: SPDX-2.3\nSPDXID: SPDXRef-DOCUMENT\n\n" + + "PackageName: a\nSPDXID: SPDXRef-a\nPackageSourceInfo: acquired package info from installed python package manifest file: /poetry.lock\n\n" + + "PackageName: b\nSPDXID: SPDXRef-b\n\n" + + "Relationship: SPDXRef-b DEPENDENCY_OF SPDXRef-a\n", + } + for name, input := range inputs { + t.Run(name, func(t *testing.T) { + doc, err := Parse([]byte(input)) + if err != nil { + t.Fatal(err) + } + want := Relationship{SourceID: "SPDXRef-a", Source: "a", TargetID: "SPDXRef-b", Target: "b", Type: RelDependsOn} + if len(doc.Relationships) != 1 || doc.Relationships[0] != want { + t.Errorf("Relationships = %+v, want [%+v]", doc.Relationships, want) + } + if got := doc.Packages[0].SourceInfo; got != "acquired package info from installed python package manifest file: /poetry.lock" { + t.Errorf("SourceInfo = %q", got) + } + }) + } +} diff --git a/testdata/cyclonedx/syft-lock/berry.cdx.json b/testdata/cyclonedx/syft-lock/berry.cdx.json new file mode 100644 index 0000000..fa63369 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/berry.cdx.json @@ -0,0 +1,145 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "8fedd5be3705aa73", + "type": "file", + "name": "berry" + } + }, + "components": [ + { + "bom-ref": "pkg:npm/accepts@1.3.8?package-id=405ed17ac849bb64", + "type": "library", + "name": "accepts", + "version": "1.3.8", + "cpe": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*", + "purl": "pkg:npm/accepts@1.3.8", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-yarn-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/yarn.lock" + } + ] + }, + { + "bom-ref": "pkg:npm/app@0.0.0-use.local?package-id=0d57075a66eeffef", + "type": "library", + "name": "app", + "version": "0.0.0-use.local", + "cpe": "cpe:2.3:a:app:app:0.0.0-use.local:*:*:*:*:*:*:*", + "purl": "pkg:npm/app@0.0.0-use.local", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-yarn-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/yarn.lock" + } + ] + }, + { + "bom-ref": "pkg:npm/express@4.18.2?package-id=c0b33931be4c6bc1", + "type": "library", + "name": "express", + "version": "4.18.2", + "cpe": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*", + "purl": "pkg:npm/express@4.18.2", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-yarn-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/yarn.lock" + } + ] + }, + { + "bom-ref": "dcee26e98fae7b36", + "type": "file", + "name": "berry/yarn.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "25106461e83f05874e95ae1ff4354d48e8d90c05" + }, + { + "alg": "SHA-256", + "content": "391d34344bad7be85707d66eb6c3de76ba8f16d30f37490af7bdcb31c3b02d6d" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/app@0.0.0-use.local?package-id=0d57075a66eeffef", + "dependsOn": [ + "pkg:npm/express@4.18.2?package-id=c0b33931be4c6bc1" + ] + }, + { + "ref": "pkg:npm/express@4.18.2?package-id=c0b33931be4c6bc1", + "dependsOn": [ + "pkg:npm/accepts@1.3.8?package-id=405ed17ac849bb64" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/cargo.cdx.json b/testdata/cyclonedx/syft-lock/cargo.cdx.json new file mode 100644 index 0000000..b7b8aaa --- /dev/null +++ b/testdata/cyclonedx/syft-lock/cargo.cdx.json @@ -0,0 +1,192 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "cf2ed2b38a4108cf", + "type": "file", + "name": "cargo" + } + }, + "components": [ + { + "bom-ref": "728030f2ab43f725", + "type": "library", + "name": "app", + "version": "0.1.0", + "cpe": "cpe:2.3:a:app_project:app:0.1.0:*:*:*:*:rust:*:*", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rust-cargo-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "rust" + }, + { + "name": "syft:package:type", + "value": "rust-crate" + }, + { + "name": "syft:package:metadataType", + "value": "rust-cargo-lock-entry" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:app:app:0.1.0:*:*:*:*:rust:*:*" + }, + { + "name": "syft:location:0:path", + "value": "/Cargo.lock" + } + ] + }, + { + "bom-ref": "pkg:cargo/serde@1.0.190?package-id=9981dc59cb3e1cb9", + "type": "library", + "name": "serde", + "version": "1.0.190", + "cpe": "cpe:2.3:a:serde_project:serde:1.0.190:*:*:*:*:rust:*:*", + "purl": "pkg:cargo/serde@1.0.190", + "externalReferences": [ + { + "url": "registry+https://github.com/rust-lang/crates.io-index", + "type": "distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rust-cargo-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "rust" + }, + { + "name": "syft:package:type", + "value": "rust-crate" + }, + { + "name": "syft:package:metadataType", + "value": "rust-cargo-lock-entry" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde:serde:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:location:0:path", + "value": "/Cargo.lock" + } + ] + }, + { + "bom-ref": "pkg:cargo/serde_derive@1.0.190?package-id=339127215a83e280", + "type": "library", + "name": "serde_derive", + "version": "1.0.190", + "cpe": "cpe:2.3:a:serde_derive_project:serde-derive:1.0.190:*:*:*:*:rust:*:*", + "purl": "pkg:cargo/serde_derive@1.0.190", + "externalReferences": [ + { + "url": "registry+https://github.com/rust-lang/crates.io-index", + "type": "distribution" + } + ], + "properties": [ + { + "name": "syft:package:foundBy", + "value": "rust-cargo-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "rust" + }, + { + "name": "syft:package:type", + "value": "rust-crate" + }, + { + "name": "syft:package:metadataType", + "value": "rust-cargo-lock-entry" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde_derive_project:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde-derive:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde-derive:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde_derive:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde_derive:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:serde:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "name": "syft:location:0:path", + "value": "/Cargo.lock" + } + ] + }, + { + "bom-ref": "c6bea2c24af05bc1", + "type": "file", + "name": "cargo/Cargo.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "df758add487e58b2c5457b120f531158bb96b89d" + }, + { + "alg": "SHA-256", + "content": "8e2de07cf4632c9e09df9a44ad97ce9a402b10ad168101805ca99a5ae79fd0e1" + } + ] + } + ], + "dependencies": [ + { + "ref": "728030f2ab43f725", + "dependsOn": [ + "pkg:cargo/serde@1.0.190?package-id=9981dc59cb3e1cb9" + ] + }, + { + "ref": "pkg:cargo/serde@1.0.190?package-id=9981dc59cb3e1cb9", + "dependsOn": [ + "pkg:cargo/serde_derive@1.0.190?package-id=339127215a83e280" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/npm.cdx.json b/testdata/cyclonedx/syft-lock/npm.cdx.json new file mode 100644 index 0000000..c743db7 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/npm.cdx.json @@ -0,0 +1,145 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "d8b0ae186b8a4344", + "type": "file", + "name": "npm" + } + }, + "components": [ + { + "bom-ref": "pkg:npm/accepts@1.3.8?package-id=27587463afb4ef54", + "type": "library", + "name": "accepts", + "version": "1.3.8", + "cpe": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*", + "purl": "pkg:npm/accepts@1.3.8", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-npm-package-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/package-lock.json" + } + ] + }, + { + "bom-ref": "pkg:npm/app@1.0.0?package-id=d612e2e66643c207", + "type": "library", + "name": "app", + "version": "1.0.0", + "cpe": "cpe:2.3:a:app:app:1.0.0:*:*:*:*:*:*:*", + "purl": "pkg:npm/app@1.0.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-npm-package-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/package-lock.json" + } + ] + }, + { + "bom-ref": "pkg:npm/express@4.18.2?package-id=c3444b233839bfbe", + "type": "library", + "name": "express", + "version": "4.18.2", + "cpe": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*", + "purl": "pkg:npm/express@4.18.2", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-npm-package-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/package-lock.json" + } + ] + }, + { + "bom-ref": "fd71c2238fc07657", + "type": "file", + "name": "npm/package-lock.json", + "hashes": [ + { + "alg": "SHA-1", + "content": "cb30ec046e1244667495b6eaedaf0d69dfedcd7e" + }, + { + "alg": "SHA-256", + "content": "80fbfd82abcabbd080a02de72c130647c1194af899ff99ab3c9997bb23d5de00" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/app@1.0.0?package-id=d612e2e66643c207", + "dependsOn": [ + "pkg:npm/express@4.18.2?package-id=c3444b233839bfbe" + ] + }, + { + "ref": "pkg:npm/express@4.18.2?package-id=c3444b233839bfbe", + "dependsOn": [ + "pkg:npm/accepts@1.3.8?package-id=27587463afb4ef54" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/pdm.cdx.json b/testdata/cyclonedx/syft-lock/pdm.cdx.json new file mode 100644 index 0000000..14246f1 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/pdm.cdx.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "d8a9ce186b845ab6", + "type": "file", + "name": "pdm" + } + }, + "components": [ + { + "bom-ref": "pkg:pypi/requests@2.31.0?package-id=d97d1ee1c7a2d435", + "type": "library", + "name": "requests", + "version": "2.31.0", + "cpe": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/requests@2.31.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-pdm-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/pdm.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/urllib3@2.1.0?package-id=0479d648f8aa7541", + "type": "library", + "name": "urllib3", + "version": "2.1.0", + "cpe": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/urllib3@2.1.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-pdm-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/pdm.lock" + } + ] + }, + { + "bom-ref": "2b9d7aa4cee17183", + "type": "file", + "name": "pdm/pdm.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "6bf2a56cff65829ecf4f977a4317fddae2a2fa4f" + }, + { + "alg": "SHA-256", + "content": "e7cb9f4512f9d1f2e047d4f4b8f28047f6ad0a3d5df7b647731a70cdd7d77d1b" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/requests@2.31.0?package-id=d97d1ee1c7a2d435", + "dependsOn": [ + "pkg:pypi/urllib3@2.1.0?package-id=0479d648f8aa7541" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/pnpm9.cdx.json b/testdata/cyclonedx/syft-lock/pnpm9.cdx.json new file mode 100644 index 0000000..7e5f28a --- /dev/null +++ b/testdata/cyclonedx/syft-lock/pnpm9.cdx.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "7a911b482d938b9b", + "type": "file", + "name": "pnpm9" + } + }, + "components": [ + { + "bom-ref": "pkg:npm/accepts@1.3.8?package-id=67cae3e45f56cc09", + "type": "library", + "name": "accepts", + "version": "1.3.8", + "cpe": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*", + "purl": "pkg:npm/accepts@1.3.8", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-pnpm-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/pnpm-lock.yaml" + } + ] + }, + { + "bom-ref": "pkg:npm/express@4.18.2?package-id=4edbc0b4eb81a972", + "type": "library", + "name": "express", + "version": "4.18.2", + "cpe": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*", + "purl": "pkg:npm/express@4.18.2", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-pnpm-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/pnpm-lock.yaml" + } + ] + }, + { + "bom-ref": "c895fb8e872e275f", + "type": "file", + "name": "pnpm9/pnpm-lock.yaml", + "hashes": [ + { + "alg": "SHA-1", + "content": "3e78d60ae2bd41cbc94df2aff5c2d07f3413e1e8" + }, + { + "alg": "SHA-256", + "content": "c793289aec36b1e18cf298c774e621991c3810ad6c263c65adaa3aba281e2554" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/express@4.18.2?package-id=4edbc0b4eb81a972", + "dependsOn": [ + "pkg:npm/accepts@1.3.8?package-id=67cae3e45f56cc09" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/poetry-leaf.cdx.json b/testdata/cyclonedx/syft-lock/poetry-leaf.cdx.json new file mode 100644 index 0000000..9e06fe5 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/poetry-leaf.cdx.json @@ -0,0 +1,183 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "342cf4438b70b22f", + "type": "file", + "name": "poetry-leaf" + } + }, + "components": [ + { + "bom-ref": "pkg:pypi/idna@3.7?package-id=ffe8cf1585aac85a", + "type": "library", + "name": "idna", + "version": "3.7", + "cpe": "cpe:2.3:a:kjd:internationalized_domain_names_in_applications:3.7:*:*:*:*:python:*:*", + "purl": "pkg:pypi/idna@3.7", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-poetry-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/poetry.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/requests@2.32.3?package-id=92c78c0f7c6326dd", + "type": "library", + "name": "requests", + "version": "2.32.3", + "cpe": "cpe:2.3:a:python:requests:2.32.3:*:*:*:*:*:*:*", + "purl": "pkg:pypi/requests@2.32.3", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-poetry-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/poetry.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/six@1.16.0?package-id=7ce49a5bde83f07c", + "type": "library", + "name": "six", + "version": "1.16.0", + "cpe": "cpe:2.3:a:python-six:python-six:1.16.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/six@1.16.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-poetry-lock-entry" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_six:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:six:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "/poetry.lock" + } + ] + }, + { + "bom-ref": "4490038dd0eaaf1f", + "type": "file", + "name": "/private/tmp/claude-501/-Users-simon-src/147bd043-24c3-4ed4-9982-87ee6ebb9459/scratchpad/syft-fixtures/poetry-leaf/poetry.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "b68ffd690ea026643192341835a28bafc6797bb6" + }, + { + "alg": "SHA-256", + "content": "e29fca892195c674b27d379957a259df1e796dcbefa25cc251b6f3e9270f70c2" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/requests@2.32.3?package-id=92c78c0f7c6326dd", + "dependsOn": [ + "pkg:pypi/idna@3.7?package-id=ffe8cf1585aac85a" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/poetry.cdx.json b/testdata/cyclonedx/syft-lock/poetry.cdx.json new file mode 100644 index 0000000..5b02e70 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/poetry.cdx.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "3e4c549a49d8fe16", + "type": "file", + "name": "poetry" + } + }, + "components": [ + { + "bom-ref": "pkg:pypi/requests@2.31.0?package-id=9082330c5423f680", + "type": "library", + "name": "requests", + "version": "2.31.0", + "cpe": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/requests@2.31.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-poetry-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/poetry.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/urllib3@2.1.0?package-id=9d0487ec0b086dd6", + "type": "library", + "name": "urllib3", + "version": "2.1.0", + "cpe": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/urllib3@2.1.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-poetry-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/poetry.lock" + } + ] + }, + { + "bom-ref": "4490038dd0eaaf1f", + "type": "file", + "name": "poetry/poetry.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "17cac89e54b2bb92a8f9211b5581c1ce22b0dfd3" + }, + { + "alg": "SHA-256", + "content": "83c06cc1183481c13578217219eb22f778efd9e82d7b58e8c9d9302aa36f8467" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/requests@2.31.0?package-id=9082330c5423f680", + "dependsOn": [ + "pkg:pypi/urllib3@2.1.0?package-id=9d0487ec0b086dd6" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/uv.cdx.json b/testdata/cyclonedx/syft-lock/uv.cdx.json new file mode 100644 index 0000000..0f60387 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/uv.cdx.json @@ -0,0 +1,189 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "08325307b4eb15a8", + "type": "file", + "name": "uv" + } + }, + "components": [ + { + "bom-ref": "pkg:pypi/app@0.1.0?package-id=6e016116bed81381", + "type": "library", + "name": "app", + "version": "0.1.0", + "cpe": "cpe:2.3:a:python-app:python-app:0.1.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/app@0.1.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-uv-lock-entry" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_app:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:app:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python-app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python_app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:python:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:cpe23", + "value": "cpe:2.3:a:app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "name": "syft:location:0:path", + "value": "/uv.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/requests@2.31.0?package-id=2afe8646f430114f", + "type": "library", + "name": "requests", + "version": "2.31.0", + "cpe": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/requests@2.31.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-uv-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/uv.lock" + } + ] + }, + { + "bom-ref": "pkg:pypi/urllib3@2.1.0?package-id=075fcf4a5d5325d1", + "type": "library", + "name": "urllib3", + "version": "2.1.0", + "cpe": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*", + "purl": "pkg:pypi/urllib3@2.1.0", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "python-package-cataloger" + }, + { + "name": "syft:package:language", + "value": "python" + }, + { + "name": "syft:package:type", + "value": "python" + }, + { + "name": "syft:package:metadataType", + "value": "python-uv-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/uv.lock" + } + ] + }, + { + "bom-ref": "3717ae13dd3eb744", + "type": "file", + "name": "uv/uv.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "b5474082928bfe15e3e51b6ac6a4c30c168cfbb0" + }, + { + "alg": "SHA-256", + "content": "b8cb9de778427354ad5ed9e75092034fde14bcc9a009edd2770537a75c59fd6f" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/app@0.1.0?package-id=6e016116bed81381", + "dependsOn": [ + "pkg:pypi/requests@2.31.0?package-id=2afe8646f430114f" + ] + }, + { + "ref": "pkg:pypi/requests@2.31.0?package-id=2afe8646f430114f", + "dependsOn": [ + "pkg:pypi/urllib3@2.1.0?package-id=075fcf4a5d5325d1" + ] + } + ] +} diff --git a/testdata/cyclonedx/syft-lock/yarn.cdx.json b/testdata/cyclonedx/syft-lock/yarn.cdx.json new file mode 100644 index 0000000..f34a774 --- /dev/null +++ b/testdata/cyclonedx/syft-lock/yarn.cdx.json @@ -0,0 +1,109 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.7", + "version": 1, + "metadata": { + "timestamp": "2026-10-30T00:00:00Z", + "tools": { + "components": [ + { + "type": "application", + "author": "anchore", + "name": "syft", + "version": "1.55.0" + } + ] + }, + "component": { + "bom-ref": "60b26b7e873ded8f", + "type": "file", + "name": "yarn" + } + }, + "components": [ + { + "bom-ref": "pkg:npm/accepts@1.3.8?package-id=16547953eed403c0", + "type": "library", + "name": "accepts", + "version": "1.3.8", + "cpe": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*", + "purl": "pkg:npm/accepts@1.3.8", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-yarn-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/yarn.lock" + } + ] + }, + { + "bom-ref": "pkg:npm/express@4.18.2?package-id=6c046d5d80ebc406", + "type": "library", + "name": "express", + "version": "4.18.2", + "cpe": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*", + "purl": "pkg:npm/express@4.18.2", + "properties": [ + { + "name": "syft:package:foundBy", + "value": "javascript-lock-cataloger" + }, + { + "name": "syft:package:language", + "value": "javascript" + }, + { + "name": "syft:package:type", + "value": "npm" + }, + { + "name": "syft:package:metadataType", + "value": "javascript-yarn-lock-entry" + }, + { + "name": "syft:location:0:path", + "value": "/yarn.lock" + } + ] + }, + { + "bom-ref": "dcee26e98fae7b36", + "type": "file", + "name": "yarn/yarn.lock", + "hashes": [ + { + "alg": "SHA-1", + "content": "dedc73362908644f427cffb952d559bdeeaf0429" + }, + { + "alg": "SHA-256", + "content": "22f8183fd74d8b014155173f3efd3d7ae2aaaae9e210b76972148890cc0c5b6d" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/express@4.18.2?package-id=6c046d5d80ebc406", + "dependsOn": [ + "pkg:npm/accepts@1.3.8?package-id=16547953eed403c0" + ] + } + ] +} diff --git a/testdata/spdx/syft-lock/berry.spdx.json b/testdata/spdx/syft-lock/berry.spdx.json new file mode 100644 index 0000000..820ce40 --- /dev/null +++ b/testdata/spdx/syft-lock/berry.spdx.json @@ -0,0 +1,174 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "berry", + "documentNamespace": "https://anchore.com/syft/dir/berry", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "accepts", + "SPDXID": "SPDXRef-Package-npm-accepts-405ed17ac849bb64", + "versionInfo": "1.3.8", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/accepts@1.3.8" + } + ] + }, + { + "name": "app", + "SPDXID": "SPDXRef-Package-npm-app-0d57075a66eeffef", + "versionInfo": "0.0.0-use.local", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:app:0.0.0-use.local:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/app@0.0.0-use.local" + } + ] + }, + { + "name": "express", + "SPDXID": "SPDXRef-Package-npm-express-c0b33931be4c6bc1", + "versionInfo": "4.18.2", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/express@4.18.2" + } + ] + }, + { + "name": "berry", + "SPDXID": "SPDXRef-DocumentRoot-Directory-berry", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "yarn.lock", + "SPDXID": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "25106461e83f05874e95ae1ff4354d48e8d90c05" + }, + { + "algorithm": "SHA256", + "checksumValue": "391d34344bad7be85707d66eb6c3de76ba8f16d30f37490af7bdcb31c3b02d6d" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-npm-accepts-405ed17ac849bb64", + "relatedSpdxElement": "SPDXRef-Package-npm-express-c0b33931be4c6bc1", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-c0b33931be4c6bc1", + "relatedSpdxElement": "SPDXRef-Package-npm-app-0d57075a66eeffef", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-app-0d57075a66eeffef", + "relatedSpdxElement": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-accepts-405ed17ac849bb64", + "relatedSpdxElement": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-c0b33931be4c6bc1", + "relatedSpdxElement": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-berry", + "relatedSpdxElement": "SPDXRef-Package-npm-accepts-405ed17ac849bb64", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-berry", + "relatedSpdxElement": "SPDXRef-Package-npm-app-0d57075a66eeffef", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-berry", + "relatedSpdxElement": "SPDXRef-Package-npm-express-c0b33931be4c6bc1", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-berry", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/cargo.spdx.json b/testdata/spdx/syft-lock/cargo.spdx.json new file mode 100644 index 0000000..9dfaf08 --- /dev/null +++ b/testdata/spdx/syft-lock/cargo.spdx.json @@ -0,0 +1,214 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "cargo", + "documentNamespace": "https://anchore.com/syft/dir/cargo", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "app", + "SPDXID": "SPDXRef-Package-rust-crate-app-728030f2ab43f725", + "versionInfo": "0.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from rust cargo manifest: /Cargo.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app_project:app:0.1.0:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:app:0.1.0:*:*:*:*:rust:*:*" + } + ] + }, + { + "name": "serde", + "SPDXID": "SPDXRef-Package-rust-crate-serde-9981dc59cb3e1cb9", + "versionInfo": "1.0.190", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from rust cargo manifest: /Cargo.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde_project:serde:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde:serde:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:cargo/serde@1.0.190" + } + ] + }, + { + "name": "serde_derive", + "SPDXID": "SPDXRef-Package-rust-crate-serde-derive-339127215a83e280", + "versionInfo": "1.0.190", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from rust cargo manifest: /Cargo.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde_derive_project:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde_derive_project:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde-derive:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde-derive:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde_derive:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde_derive:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde:serde-derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:serde:serde_derive:1.0.190:*:*:*:*:rust:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:cargo/serde_derive@1.0.190" + } + ] + }, + { + "name": "cargo", + "SPDXID": "SPDXRef-DocumentRoot-Directory-cargo", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "Cargo.lock", + "SPDXID": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "df758add487e58b2c5457b120f531158bb96b89d" + }, + { + "algorithm": "SHA256", + "checksumValue": "8e2de07cf4632c9e09df9a44ad97ce9a402b10ad168101805ca99a5ae79fd0e1" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-rust-crate-serde-derive-339127215a83e280", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-serde-9981dc59cb3e1cb9", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-rust-crate-serde-9981dc59cb3e1cb9", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-app-728030f2ab43f725", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-rust-crate-serde-derive-339127215a83e280", + "relatedSpdxElement": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-rust-crate-app-728030f2ab43f725", + "relatedSpdxElement": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-rust-crate-serde-9981dc59cb3e1cb9", + "relatedSpdxElement": "SPDXRef-File-Cargo.lock-c6bea2c24af05bc1", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-cargo", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-app-728030f2ab43f725", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-cargo", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-serde-9981dc59cb3e1cb9", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-cargo", + "relatedSpdxElement": "SPDXRef-Package-rust-crate-serde-derive-339127215a83e280", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-cargo", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/npm.spdx.json b/testdata/spdx/syft-lock/npm.spdx.json new file mode 100644 index 0000000..e85658a --- /dev/null +++ b/testdata/spdx/syft-lock/npm.spdx.json @@ -0,0 +1,174 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "npm", + "documentNamespace": "https://anchore.com/syft/dir/npm", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "accepts", + "SPDXID": "SPDXRef-Package-npm-accepts-27587463afb4ef54", + "versionInfo": "1.3.8", + "supplier": "NOASSERTION", + "downloadLocation": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /package-lock.json", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/accepts@1.3.8" + } + ] + }, + { + "name": "app", + "SPDXID": "SPDXRef-Package-npm-app-d612e2e66643c207", + "versionInfo": "1.0.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /package-lock.json", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:app:1.0.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/app@1.0.0" + } + ] + }, + { + "name": "express", + "SPDXID": "SPDXRef-Package-npm-express-c3444b233839bfbe", + "versionInfo": "4.18.2", + "supplier": "NOASSERTION", + "downloadLocation": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /package-lock.json", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/express@4.18.2" + } + ] + }, + { + "name": "npm", + "SPDXID": "SPDXRef-DocumentRoot-Directory-npm", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "package-lock.json", + "SPDXID": "SPDXRef-File-package-lock.json-fd71c2238fc07657", + "fileTypes": [ + "APPLICATION" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "cb30ec046e1244667495b6eaedaf0d69dfedcd7e" + }, + { + "algorithm": "SHA256", + "checksumValue": "80fbfd82abcabbd080a02de72c130647c1194af899ff99ab3c9997bb23d5de00" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-npm-accepts-27587463afb4ef54", + "relatedSpdxElement": "SPDXRef-Package-npm-express-c3444b233839bfbe", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-c3444b233839bfbe", + "relatedSpdxElement": "SPDXRef-Package-npm-app-d612e2e66643c207", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-accepts-27587463afb4ef54", + "relatedSpdxElement": "SPDXRef-File-package-lock.json-fd71c2238fc07657", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-c3444b233839bfbe", + "relatedSpdxElement": "SPDXRef-File-package-lock.json-fd71c2238fc07657", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-app-d612e2e66643c207", + "relatedSpdxElement": "SPDXRef-File-package-lock.json-fd71c2238fc07657", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-npm", + "relatedSpdxElement": "SPDXRef-Package-npm-accepts-27587463afb4ef54", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-npm", + "relatedSpdxElement": "SPDXRef-Package-npm-app-d612e2e66643c207", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-npm", + "relatedSpdxElement": "SPDXRef-Package-npm-express-c3444b233839bfbe", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-npm", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/pdm.spdx.json b/testdata/spdx/syft-lock/pdm.spdx.json new file mode 100644 index 0000000..0b345de --- /dev/null +++ b/testdata/spdx/syft-lock/pdm.spdx.json @@ -0,0 +1,134 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "pdm", + "documentNamespace": "https://anchore.com/syft/dir/pdm", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "requests", + "SPDXID": "SPDXRef-Package-python-requests-d97d1ee1c7a2d435", + "versionInfo": "2.31.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /pdm.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/requests@2.31.0" + } + ] + }, + { + "name": "urllib3", + "SPDXID": "SPDXRef-Package-python-urllib3-0479d648f8aa7541", + "versionInfo": "2.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /pdm.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/urllib3@2.1.0" + } + ] + }, + { + "name": "pdm", + "SPDXID": "SPDXRef-DocumentRoot-Directory-pdm", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "pdm.lock", + "SPDXID": "SPDXRef-File-pdm.lock-2b9d7aa4cee17183", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "6bf2a56cff65829ecf4f977a4317fddae2a2fa4f" + }, + { + "algorithm": "SHA256", + "checksumValue": "e7cb9f4512f9d1f2e047d4f4b8f28047f6ad0a3d5df7b647731a70cdd7d77d1b" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-python-urllib3-0479d648f8aa7541", + "relatedSpdxElement": "SPDXRef-Package-python-requests-d97d1ee1c7a2d435", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-python-urllib3-0479d648f8aa7541", + "relatedSpdxElement": "SPDXRef-File-pdm.lock-2b9d7aa4cee17183", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-requests-d97d1ee1c7a2d435", + "relatedSpdxElement": "SPDXRef-File-pdm.lock-2b9d7aa4cee17183", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-pdm", + "relatedSpdxElement": "SPDXRef-Package-python-requests-d97d1ee1c7a2d435", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-pdm", + "relatedSpdxElement": "SPDXRef-Package-python-urllib3-0479d648f8aa7541", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-pdm", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/pnpm9.spdx.json b/testdata/spdx/syft-lock/pnpm9.spdx.json new file mode 100644 index 0000000..d1db34f --- /dev/null +++ b/testdata/spdx/syft-lock/pnpm9.spdx.json @@ -0,0 +1,134 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "pnpm9", + "documentNamespace": "https://anchore.com/syft/dir/pnpm9", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "accepts", + "SPDXID": "SPDXRef-Package-npm-accepts-67cae3e45f56cc09", + "versionInfo": "1.3.8", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /pnpm-lock.yaml", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/accepts@1.3.8" + } + ] + }, + { + "name": "express", + "SPDXID": "SPDXRef-Package-npm-express-4edbc0b4eb81a972", + "versionInfo": "4.18.2", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /pnpm-lock.yaml", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/express@4.18.2" + } + ] + }, + { + "name": "pnpm9", + "SPDXID": "SPDXRef-DocumentRoot-Directory-pnpm9", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "pnpm-lock.yaml", + "SPDXID": "SPDXRef-File-pnpm-lock.yaml-c895fb8e872e275f", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "3e78d60ae2bd41cbc94df2aff5c2d07f3413e1e8" + }, + { + "algorithm": "SHA256", + "checksumValue": "c793289aec36b1e18cf298c774e621991c3810ad6c263c65adaa3aba281e2554" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-npm-accepts-67cae3e45f56cc09", + "relatedSpdxElement": "SPDXRef-Package-npm-express-4edbc0b4eb81a972", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-4edbc0b4eb81a972", + "relatedSpdxElement": "SPDXRef-File-pnpm-lock.yaml-c895fb8e872e275f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-accepts-67cae3e45f56cc09", + "relatedSpdxElement": "SPDXRef-File-pnpm-lock.yaml-c895fb8e872e275f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-pnpm9", + "relatedSpdxElement": "SPDXRef-Package-npm-accepts-67cae3e45f56cc09", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-pnpm9", + "relatedSpdxElement": "SPDXRef-Package-npm-express-4edbc0b4eb81a972", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-pnpm9", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/poetry-leaf.spdx.json b/testdata/spdx/syft-lock/poetry-leaf.spdx.json new file mode 100644 index 0000000..8231e51 --- /dev/null +++ b/testdata/spdx/syft-lock/poetry-leaf.spdx.json @@ -0,0 +1,224 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "poetry-leaf", + "documentNamespace": "https://anchore.com/syft/dir/poetry-leaf", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "idna", + "SPDXID": "SPDXRef-Package-python-idna-ffe8cf1585aac85a", + "versionInfo": "3.7", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:kjd:internationalized_domain_names_in_applications:3.7:*:*:*:*:python:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/idna@3.7" + } + ] + }, + { + "name": "requests", + "SPDXID": "SPDXRef-Package-python-requests-92c78c0f7c6326dd", + "versionInfo": "2.32.3", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:requests:2.32.3:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/requests@2.32.3" + } + ] + }, + { + "name": "six", + "SPDXID": "SPDXRef-Package-python-six-7ce49a5bde83f07c", + "versionInfo": "1.16.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-six:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_six:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:six:python-six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:six:python_six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:six:six:1.16.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/six@1.16.0" + } + ] + }, + { + "name": "poetry-leaf", + "SPDXID": "SPDXRef-DocumentRoot-Directory-poetry-leaf", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "poetry.lock", + "SPDXID": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b68ffd690ea026643192341835a28bafc6797bb6" + }, + { + "algorithm": "SHA256", + "checksumValue": "e29fca892195c674b27d379957a259df1e796dcbefa25cc251b6f3e9270f70c2" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-python-idna-ffe8cf1585aac85a", + "relatedSpdxElement": "SPDXRef-Package-python-requests-92c78c0f7c6326dd", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-python-six-7ce49a5bde83f07c", + "relatedSpdxElement": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-requests-92c78c0f7c6326dd", + "relatedSpdxElement": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-idna-ffe8cf1585aac85a", + "relatedSpdxElement": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-poetry-leaf", + "relatedSpdxElement": "SPDXRef-Package-python-idna-ffe8cf1585aac85a", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-poetry-leaf", + "relatedSpdxElement": "SPDXRef-Package-python-requests-92c78c0f7c6326dd", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-poetry-leaf", + "relatedSpdxElement": "SPDXRef-Package-python-six-7ce49a5bde83f07c", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-poetry-leaf", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/poetry.spdx.json b/testdata/spdx/syft-lock/poetry.spdx.json new file mode 100644 index 0000000..7dd1c57 --- /dev/null +++ b/testdata/spdx/syft-lock/poetry.spdx.json @@ -0,0 +1,134 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "poetry", + "documentNamespace": "https://anchore.com/syft/dir/poetry", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "requests", + "SPDXID": "SPDXRef-Package-python-requests-9082330c5423f680", + "versionInfo": "2.31.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/requests@2.31.0" + } + ] + }, + { + "name": "urllib3", + "SPDXID": "SPDXRef-Package-python-urllib3-9d0487ec0b086dd6", + "versionInfo": "2.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /poetry.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/urllib3@2.1.0" + } + ] + }, + { + "name": "poetry", + "SPDXID": "SPDXRef-DocumentRoot-Directory-poetry", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "poetry.lock", + "SPDXID": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "17cac89e54b2bb92a8f9211b5581c1ce22b0dfd3" + }, + { + "algorithm": "SHA256", + "checksumValue": "83c06cc1183481c13578217219eb22f778efd9e82d7b58e8c9d9302aa36f8467" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-python-urllib3-9d0487ec0b086dd6", + "relatedSpdxElement": "SPDXRef-Package-python-requests-9082330c5423f680", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-python-requests-9082330c5423f680", + "relatedSpdxElement": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-urllib3-9d0487ec0b086dd6", + "relatedSpdxElement": "SPDXRef-File-poetry.lock-4490038dd0eaaf1f", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-poetry", + "relatedSpdxElement": "SPDXRef-Package-python-requests-9082330c5423f680", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-poetry", + "relatedSpdxElement": "SPDXRef-Package-python-urllib3-9d0487ec0b086dd6", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-poetry", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/uv.spdx.json b/testdata/spdx/syft-lock/uv.spdx.json new file mode 100644 index 0000000..6a3e067 --- /dev/null +++ b/testdata/spdx/syft-lock/uv.spdx.json @@ -0,0 +1,229 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "uv", + "documentNamespace": "https://anchore.com/syft/dir/uv", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "app", + "SPDXID": "SPDXRef-Package-python-app-6e016116bed81381", + "versionInfo": "0.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /uv.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-app:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_app:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:python-app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:python_app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python-app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python_app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:app:app:0.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/app@0.1.0" + } + ] + }, + { + "name": "requests", + "SPDXID": "SPDXRef-Package-python-requests-2afe8646f430114f", + "versionInfo": "2.31.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /uv.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:requests:2.31.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/requests@2.31.0" + } + ] + }, + { + "name": "urllib3", + "SPDXID": "SPDXRef-Package-python-urllib3-075fcf4a5d5325d1", + "versionInfo": "2.1.0", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed python package manifest file: /uv.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:python:urllib3:2.1.0:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:pypi/urllib3@2.1.0" + } + ] + }, + { + "name": "uv", + "SPDXID": "SPDXRef-DocumentRoot-Directory-uv", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "uv.lock", + "SPDXID": "SPDXRef-File-uv.lock-3717ae13dd3eb744", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "b5474082928bfe15e3e51b6ac6a4c30c168cfbb0" + }, + { + "algorithm": "SHA256", + "checksumValue": "b8cb9de778427354ad5ed9e75092034fde14bcc9a009edd2770537a75c59fd6f" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-python-urllib3-075fcf4a5d5325d1", + "relatedSpdxElement": "SPDXRef-Package-python-requests-2afe8646f430114f", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-python-requests-2afe8646f430114f", + "relatedSpdxElement": "SPDXRef-Package-python-app-6e016116bed81381", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-python-urllib3-075fcf4a5d5325d1", + "relatedSpdxElement": "SPDXRef-File-uv.lock-3717ae13dd3eb744", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-requests-2afe8646f430114f", + "relatedSpdxElement": "SPDXRef-File-uv.lock-3717ae13dd3eb744", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-python-app-6e016116bed81381", + "relatedSpdxElement": "SPDXRef-File-uv.lock-3717ae13dd3eb744", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-uv", + "relatedSpdxElement": "SPDXRef-Package-python-app-6e016116bed81381", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-uv", + "relatedSpdxElement": "SPDXRef-Package-python-requests-2afe8646f430114f", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-uv", + "relatedSpdxElement": "SPDXRef-Package-python-urllib3-075fcf4a5d5325d1", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-uv", + "relationshipType": "DESCRIBES" + } + ] +} diff --git a/testdata/spdx/syft-lock/yarn.spdx.json b/testdata/spdx/syft-lock/yarn.spdx.json new file mode 100644 index 0000000..34b472d --- /dev/null +++ b/testdata/spdx/syft-lock/yarn.spdx.json @@ -0,0 +1,134 @@ +{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "yarn", + "documentNamespace": "https://anchore.com/syft/dir/yarn", + "creationInfo": { + "licenseListVersion": "3.29", + "creators": [ + "Organization: Anchore, Inc", + "Tool: syft-1.55.0" + ], + "created": "2026-10-30T00:00:00Z" + }, + "packages": [ + { + "name": "accepts", + "SPDXID": "SPDXRef-Package-npm-accepts-16547953eed403c0", + "versionInfo": "1.3.8", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:accepts:accepts:1.3.8:*:*:*:*:*:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/accepts@1.3.8" + } + ] + }, + { + "name": "express", + "SPDXID": "SPDXRef-Package-npm-express-6c046d5d80ebc406", + "versionInfo": "4.18.2", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceType": "cpe23Type", + "referenceLocator": "cpe:2.3:a:openjsf:express:4.18.2:*:*:*:*:node.js:*:*" + }, + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/express@4.18.2" + } + ] + }, + { + "name": "yarn", + "SPDXID": "SPDXRef-DocumentRoot-Directory-yarn", + "supplier": "NOASSERTION", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "FILE" + } + ], + "files": [ + { + "fileName": "yarn.lock", + "SPDXID": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "fileTypes": [ + "TEXT" + ], + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "dedc73362908644f427cffb952d559bdeeaf0429" + }, + { + "algorithm": "SHA256", + "checksumValue": "22f8183fd74d8b014155173f3efd3d7ae2aaaae9e210b76972148890cc0c5b6d" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "relationships": [ + { + "spdxElementId": "SPDXRef-Package-npm-accepts-16547953eed403c0", + "relatedSpdxElement": "SPDXRef-Package-npm-express-6c046d5d80ebc406", + "relationshipType": "DEPENDENCY_OF" + }, + { + "spdxElementId": "SPDXRef-Package-npm-accepts-16547953eed403c0", + "relatedSpdxElement": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-Package-npm-express-6c046d5d80ebc406", + "relatedSpdxElement": "SPDXRef-File-yarn.lock-dcee26e98fae7b36", + "relationshipType": "OTHER", + "comment": "evident-by: indicates the package's existence is evident by the given file" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-yarn", + "relatedSpdxElement": "SPDXRef-Package-npm-accepts-16547953eed403c0", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DocumentRoot-Directory-yarn", + "relatedSpdxElement": "SPDXRef-Package-npm-express-6c046d5d80ebc406", + "relationshipType": "CONTAINS" + }, + { + "spdxElementId": "SPDXRef-DOCUMENT", + "relatedSpdxElement": "SPDXRef-DocumentRoot-Directory-yarn", + "relationshipType": "DESCRIBES" + } + ] +}