From 437e2675b34f8de07f21842c454dde6cde784a21 Mon Sep 17 00:00:00 2001 From: mikcl Date: Fri, 18 Nov 2022 22:53:43 +0000 Subject: [PATCH] npm-aliases: handle aliases for package-lock.json Signed-off-by: mikcl --- syft/pkg/cataloger/javascript/package.go | 19 +++++- .../javascript/parse_package_lock.go | 5 ++ .../javascript/parse_package_lock_test.go | 54 +++++++++++++++++ .../pkg-lock/alias-package-lock-1.json | 23 ++++++++ .../pkg-lock/alias-package-lock-2.json | 58 +++++++++++++++++++ 5 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-1.json create mode 100644 syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-2.json diff --git a/syft/pkg/cataloger/javascript/package.go b/syft/pkg/cataloger/javascript/package.go index 98006d65103..4c89f49ce45 100644 --- a/syft/pkg/cataloger/javascript/package.go +++ b/syft/pkg/cataloger/javascript/package.go @@ -44,14 +44,29 @@ func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Pack } func newPackageLockV1Package(resolver source.FileResolver, location source.Location, name string, u lockDependency) pkg.Package { + version := u.Version + + const aliasPrefixPackageLockV1 = "npm:" + + // Handles type aliases https://github.com/npm/rfcs/blob/main/implemented/0001-package-aliases.md + if strings.HasPrefix(version, aliasPrefixPackageLockV1) { + // this is an alias. + // `"version": "npm:canonical-name@X.Y.Z"` + canonicalPackageAndVersion := version[len(aliasPrefixPackageLockV1):] + versionSeparator := strings.LastIndex(canonicalPackageAndVersion, "@") + + name = canonicalPackageAndVersion[:versionSeparator] + version = canonicalPackageAndVersion[versionSeparator+1:] + } + return finalizeLockPkg( resolver, location, pkg.Package{ Name: name, - Version: u.Version, + Version: version, Locations: source.NewLocationSet(location), - PURL: packageURL(name, u.Version), + PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, }, diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 28fba68d064..09453cd9edd 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -75,6 +75,11 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read } } + // handles alias names + if pkgMeta.Name != "" { + name = pkgMeta.Name + } + pkgs = append(pkgs, newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta)) } } diff --git a/syft/pkg/cataloger/javascript/parse_package_lock_test.go b/syft/pkg/cataloger/javascript/parse_package_lock_test.go index a78e3ef1278..2b305214ec0 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock_test.go @@ -193,3 +193,57 @@ func TestParsePackageLockV3(t *testing.T) { } pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) } + +func TestParsePackageLockAlias(t *testing.T) { + var expectedRelationships []artifact.Relationship + commonPkgs := []pkg.Package{ + { + Name: "case", + Version: "1.6.2", + PURL: "pkg:npm/case@1.6.2", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "case", + Version: "1.6.3", + PURL: "pkg:npm/case@1.6.3", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "@bundled-es-modules/chai", + Version: "4.2.2", + PURL: "pkg:npm/%40bundled-es-modules/chai@4.2.2", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + } + + v2Pkg := pkg.Package{ + Name: "alias-check", + Version: "1.0.0", + PURL: "pkg:npm/alias-check@1.0.0", + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Licenses: []string{"ISC"}, + } + + packageLockV1 := "test-fixtures/pkg-lock/alias-package-lock-1.json" + packageLockV2 := "test-fixtures/pkg-lock/alias-package-lock-2.json" + packageLocks := []string{packageLockV1, packageLockV2} + + for _, packageLock := range packageLocks { + expected := make([]pkg.Package, len(commonPkgs)) + copy(expected, commonPkgs) + + if packageLock == packageLockV2 { + expected = append(expected, v2Pkg) + } + + for i := range expected { + expected[i].Locations.Add(source.NewLocation(packageLock)) + } + pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships) + } +} diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-1.json b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-1.json new file mode 100644 index 00000000000..f7fc133000f --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-1.json @@ -0,0 +1,23 @@ +{ + "name": "alias-check", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "case": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz", + "integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg==" + }, + "case-alias": { + "version": "npm:case@1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==" + }, + "chai": { + "version": "npm:@bundled-es-modules/chai@4.2.2", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz", + "integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg==" + } + } +} diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-2.json b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-2.json new file mode 100644 index 00000000000..c9de6cffa4b --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/alias-package-lock-2.json @@ -0,0 +1,58 @@ +{ + "name": "alias-check", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "alias-check", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "case": "1.6.2", + "case-alias": "npm:case@^1.6.3", + "chai": "npm:@bundled-es-modules/chai@^4.2.2" + } + }, + "node_modules/case": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz", + "integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/case-alias": { + "name": "case", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chai": { + "name": "@bundled-es-modules/chai", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz", + "integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg==" + } + }, + "dependencies": { + "case": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz", + "integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg==" + }, + "case-alias": { + "version": "npm:case@1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==" + }, + "chai": { + "version": "npm:@bundled-es-modules/chai@4.2.2", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz", + "integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg==" + } + } +}