diff --git a/syft/pkg/cataloger/javascript/cataloger.go b/syft/pkg/cataloger/javascript/cataloger.go index 80b4d8077b7f..2af8edbec7be 100644 --- a/syft/pkg/cataloger/javascript/cataloger.go +++ b/syft/pkg/cataloger/javascript/cataloger.go @@ -76,7 +76,7 @@ func addLicenses(resolver source.FileResolver, location source.Location, p *pkg. return nil } - p.Licenses = licenses + p.Licenses = append(p.Licenses, licenses...) } return nil diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 30c2897b02ce..5b98135bc6aa 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "strings" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" @@ -18,6 +19,7 @@ type PackageLock struct { Requires bool `json:"requires"` LockfileVersion int `json:"lockfileVersion"` Dependencies map[string]Dependency + Packages map[string]Package } // Dependency represents a single package dependency listed in the package.lock json file @@ -25,7 +27,13 @@ type Dependency struct { Version string `json:"version"` Resolved string `json:"resolved"` Integrity string `json:"integrity"` - Requires map[string]string +} + +type Package struct { + Version string `json:"version"` + Resolved string `json:"resolved"` + Integrity string `json:"integrity"` + License string `json:""` } // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. @@ -46,12 +54,28 @@ func parsePackageLock(path string, reader io.Reader) ([]*pkg.Package, []artifact } else if err != nil { return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err) } + licenseMap := make(map[string]string) + for _, pkgMeta := range lock.Packages { + var sb strings.Builder + sb.WriteString(pkgMeta.Resolved) + sb.WriteString(pkgMeta.Integrity) + licenseMap[sb.String()] = pkgMeta.License + } + for name, pkgMeta := range lock.Dependencies { + var sb strings.Builder + sb.WriteString(pkgMeta.Resolved) + sb.WriteString(pkgMeta.Integrity) + var licenses []string + if license, exists := licenseMap[sb.String()]; exists { + licenses = append(licenses, license) + } packages = append(packages, &pkg.Package{ Name: name, Version: pkgMeta.Version, Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: licenses, }) } } diff --git a/syft/pkg/cataloger/javascript/parse_package_lock_test.go b/syft/pkg/cataloger/javascript/parse_package_lock_test.go index a809441d9a29..1ec2620cd97f 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock_test.go @@ -30,74 +30,36 @@ func assertPkgsEqual(t *testing.T, actual []*pkg.Package, expected map[string]pk func TestParsePackageLock(t *testing.T) { expected := map[string]pkg.Package{ - "@actions/core": { - Name: "@actions/core", - Version: "1.6.0", + "@types/prop-types": { + Name: "@types/prop-types", + Version: "15.7.5", Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, }, - "wordwrap": { - Name: "wordwrap", - Version: "0.0.3", + "@types/react": { + Name: "@types/prop-types", + Version: "18.0.17", Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, }, - "get-stdin": { - Name: "get-stdin", - Version: "5.0.1", + "@types/scheduler": { + Name: "@types/scheduler", + Version: "0.16.2", Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, }, - "minimist": { - Name: "minimist", - Version: "0.0.10", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "optimist": { - Name: "optimist", - Version: "0.6.1", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "string-width": { - Name: "string-width", - Version: "2.1.1", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "strip-ansi": { - Name: "strip-ansi", - Version: "4.0.0", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "strip-eof": { - Name: "wordwrap", - Version: "1.0.0", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "ansi-regex": { - Name: "ansi-regex", - Version: "3.0.0", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "is-fullwidth-code-point": { - Name: "is-fullwidth-code-point", - Version: "2.0.0", - Language: pkg.JavaScript, - Type: pkg.NpmPkg, - }, - "cowsay": { - Name: "cowsay", - Version: "1.4.0", + "csstype": { + Name: "csstype", + Version: "3.1.0", Language: pkg.JavaScript, Type: pkg.NpmPkg, + Licenses: []string{"MIT"}, }, } - fixture, err := os.Open("test-fixtures/pkg-lock/package-lock.json") + fixture, err := os.Open("test-fixtures/pkg-lock/package-lock-2.json") if err != nil { t.Fatalf("failed to open fixture: %+v", err) } @@ -109,5 +71,4 @@ func TestParsePackageLock(t *testing.T) { } assertPkgsEqual(t, actual, expected) - } diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json new file mode 100644 index 000000000000..2373f27ca0ac --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/package-lock-2.json @@ -0,0 +1,71 @@ +{ + "name": "npm", + "version": "6.14.6", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "npm", + "version": "6.14.6", + "dependencies": { + "@types/react": "^18.0.9" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha1-XxnSuFqY6VWANvajysyIGUIPBc8=", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ=", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk=", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI=", + "license": "MIT" + } + }, + "dependencies": { + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha1-XxnSuFqY6VWANvajysyIGUIPBc8=" + }, + "@types/react": { + "version": "18.0.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", + "integrity": "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ=", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk=" + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI=" + } + } +}