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..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 filepath.Base(path) != "package-lock.json" { + 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 { 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 {