From 56c70f25c1ec9a4b8c303d9524d11bf57d7b1b13 Mon Sep 17 00:00:00 2001 From: Alexandre Laroche Date: Wed, 14 May 2025 08:06:17 -0400 Subject: [PATCH 1/2] Add support for 'npm-shrinkwrap.json' --- docs/supported_inventory_types.md | 2 +- .../packagelockjson/packagelockjson.go | 2 +- .../packagelockjson/packagelockjson_test.go | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/supported_inventory_types.md b/docs/supported_inventory_types.md index cde6e56d..71e78ccd 100644 --- a/docs/supported_inventory_types.md +++ b/docs/supported_inventory_types.md @@ -53,7 +53,7 @@ SCALIBR supports extracting software package information from a variety of OS an * Lockfiles: pom.xml, gradle.lockfile, verification-metadata.xml * Javascript * Installed NPM packages (package.json) - * Lockfiles: package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock + * Lockfiles: package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock * ObjectiveC * Podfile.lock * PHP: diff --git a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go index 59e02d01..31815061 100644 --- a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go +++ b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go @@ -237,7 +237,7 @@ func (e Extractor) Requirements() *plugin.Capabilities { // FileRequired returns true if the specified file matches npm lockfile patterns. func (e Extractor) FileRequired(api filesystem.FileAPI) bool { path := api.Path() - if filepath.Base(path) != "package-lock.json" { + if !slices.Contains([]string{"package-lock.json", "npm-shrinkwrap.json"}, filepath.Base(api.Path())) { return false } // Skip lockfiles inside node_modules directories since the packages they list aren't diff --git a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson_test.go b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson_test.go index 05e58635..d7c558b5 100644 --- a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson_test.go +++ b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson_test.go @@ -111,6 +111,33 @@ func TestExtractor_FileRequired(t *testing.T) { wantRequired: true, wantResultMetric: stats.FileRequiredResultOK, }, + { + name: "npm-shrinkwrap.json", + path: filepath.FromSlash("npm-shrinkwrap.json"), + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "npm-shrinkwrap.json at the end of a path", + path: filepath.FromSlash("path/to/my/npm-shrinkwrap.json"), + wantRequired: true, + wantResultMetric: stats.FileRequiredResultOK, + }, + { + name: "npm-shrinkwrap.json as path segment", + path: filepath.FromSlash("path/to/my/npm-shrinkwrap.json/file"), + wantRequired: false, + }, + { + name: "npm-shrinkwrap.json.file (wrong extension)", + path: filepath.FromSlash("path/to/my/npm-shrinkwrap.json.file"), + wantRequired: false, + }, + { + name: "path.to.my.npm-shrinkwrap.json", + path: filepath.FromSlash("path.to.my.npm-shrinkwrap.json"), + wantRequired: false, + }, } for _, tt := range tests { From 8953b0f6402064c6e78a2175802407be09283e53 Mon Sep 17 00:00:00 2001 From: Alexandre Laroche Date: Sat, 17 May 2025 08:13:52 -0400 Subject: [PATCH 2/2] skips package-lock.json extraction if there's also an npm-shrinkwrap.json file --- .../javascript/packagelockjson/packagelockjson.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go index 31815061..a7f4bcac 100644 --- a/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go +++ b/extractor/filesystem/language/javascript/packagelockjson/packagelockjson.go @@ -237,7 +237,7 @@ func (e Extractor) Requirements() *plugin.Capabilities { // FileRequired returns true if the specified file matches npm lockfile patterns. func (e Extractor) FileRequired(api filesystem.FileAPI) bool { path := api.Path() - if !slices.Contains([]string{"package-lock.json", "npm-shrinkwrap.json"}, filepath.Base(api.Path())) { + if !slices.Contains([]string{"package-lock.json", "npm-shrinkwrap.json"}, filepath.Base(path)) { return false } // Skip lockfiles inside node_modules directories since the packages they list aren't @@ -274,6 +274,16 @@ func (e Extractor) reportFileRequired(path string, fileSizeBytes int64, result s // Extract extracts packages from package-lock.json files passed through the scan input. func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { + // If both package-lock.json and npm-shrinkwrap.json are present in the root of a project, + // npm-shrinkwrap.json will take precedence and package-lock.json will be ignored. + if filepath.Base(input.Path) == "package-lock.json" { + npmShrinkwrapPath := strings.TrimSuffix(input.Path, "package-lock.json") + "npm-shrinkwrap.json" + _, err := input.FS.Open(npmShrinkwrapPath) + if err == nil { + return inventory.Inventory{}, nil + } + } + packages, err := e.extractPkgLock(ctx, input) if e.stats != nil {