Repository: google/osv-scanner
Pull request #610 (2023-10-25 05:55:29)

handle npm aliased packages
Fixes https://github.com/google/osv-scanner/issues/588

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/google/osv-scanner/pull/610?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#610](https://app.codecov.io/gh/google/osv-scanner/pull/610?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (e997f11) into [main](https://app.codecov.io/gh/google/osv-scanner/commit/c00f630aa0b909a34626ff540c136a26d4e5ec01?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (c00f630) will **increase** coverage by `0.02%`.
> Report is 6 commits behind head on main.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #610      +/-   ##
==========================================
+ Coverage   80.61%   80.64%   +0.02%     
==========================================
  Files          78       78              
  Lines        5334     5342       +8     
==========================================
+ Hits         4300     4308       +8     
  Misses        866      866              
  Partials      168      168              
```


| [Files](https://app.codecov.io/gh/google/osv-scanner/pull/610?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [pkg/lockfile/parse-npm-lock.go](https://app.codecov.io/gh/google/osv-scanner/pull/610?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL2xvY2tmaWxlL3BhcnNlLW5wbS1sb2NrLmdv) | `100.00% <100.00%> (ø)` | |



Patch:
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index d7d567f..78a2a82 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -140,3 +140,4 @@ jobs:
       base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
       upload-assets: true # upload to a new release
       draft-release: true # upload to a new draft release
+      upload-tag-name: "${{ inputs.version }}"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 90a9c25..1ea9e12 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -70,6 +70,9 @@ To run tests:
 ./scripts/run_tests.sh
 ```
 
+By default, tests that require additional dependencies beyond the go toolchain are skipped.
+Enable these tests by setting the env variable `TEST_ACCEPTANCE=true`.
+
 You can generate an HTML coverage report by running:
 
 ```shell
diff --git a/docs/Gemfile b/docs/Gemfile
index 3ef7eb7..0bffcee 100644
--- a/docs/Gemfile
+++ b/docs/Gemfile
@@ -14,7 +14,7 @@ source "https://rubygems.org"
 gem "github-pages", "~> 228", group: :jekyll_plugins
 # If you have any plugins, put them here!
 group :jekyll_plugins do
-  gem "jekyll-feed", "~> 0.12"
+  gem "jekyll-feed", "~> 0.17"
 end
 
 # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
diff --git a/docs/github-action.md b/docs/github-action.md
index 4ee0a94..ef3d757 100644
--- a/docs/github-action.md
+++ b/docs/github-action.md
@@ -83,8 +83,8 @@ jobs:
     uses: "google/osv-scanner/.github/workflows/osv-scanner-reusable.yml"
     with:
       scan-args: |-
-        ./path/to/lockfile1
-        requirements.txt:./path/to/python-lockfile2.txt
+        --lockfile=./path/to/lockfile1
+        --lockfile=requirements.txt:./path/to/python-lockfile2.txt
 ```
 ##### Default arguments
 ```yml
diff --git a/pkg/lockfile/fixtures/npm/alias.v1.json b/pkg/lockfile/fixtures/npm/alias.v1.json
new file mode 100644
index 0000000..f82e648
--- /dev/null
+++ b/pkg/lockfile/fixtures/npm/alias.v1.json
@@ -0,0 +1,34 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "babel-code-frame": {
+      "version": "npm:@babel/code-frame@7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+      "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+      "requires": {
+        "@babel/highlight": "^7.0.0"
+      }
+    },
+    "string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "requires": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      }
+    },
+    "string-width-cjs": {
+      "version": "npm:string-width@4.2.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+      "requires": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.0"
+      }
+    }
+  }
+}
diff --git a/pkg/lockfile/fixtures/npm/alias.v2.json b/pkg/lockfile/fixtures/npm/alias.v2.json
new file mode 100644
index 0000000..3a47091
--- /dev/null
+++ b/pkg/lockfile/fixtures/npm/alias.v2.json
@@ -0,0 +1,45 @@
+{
+  "name": "my-library",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "babel-code-frame": "^7.0.0",
+        "string-width": "^5.1.2",
+        "string-width-cjs": "npm:string-width@^4.2.0"
+      }
+    },
+    "node_modules/babel-code-frame": {
+      "name": "@babel/code-frame",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+      "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+      "dependencies": {
+        "@babel/highlight": "^7.0.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "dependencies": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      }
+    },
+    "node_modules/string-width-cjs": {
+      "name": "string-width",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.0"
+      }
+    }
+  },
+  "dependencies": {}
+}
diff --git a/pkg/lockfile/parse-npm-lock.go b/pkg/lockfile/parse-npm-lock.go
index c5ab855..7d0f1b4 100644
--- a/pkg/lockfile/parse-npm-lock.go
+++ b/pkg/lockfile/parse-npm-lock.go
@@ -9,11 +9,14 @@ import (
 )
 
 type NpmLockDependency struct {
+	// For an aliased package, Version is like "npm:[name]@[version]"
 	Version      string                       `json:"version"`
 	Dependencies map[string]NpmLockDependency `json:"dependencies,omitempty"`
 }
 
 type NpmLockPackage struct {
+	// For an aliased package, Name is the real package name
+	Name         string            `json:"name"`
 	Version      string            `json:"version"`
 	Resolved     string            `json:"resolved"`
 	Dependencies map[string]string `json:"dependencies"`
@@ -65,6 +68,13 @@ func parseNpmLockDependencies(dependencies map[string]NpmLockDependency) map[str
 		finalVersion := version
 		commit := ""
 
+		// If the package is aliased, get the name and version
+		if strings.HasPrefix(detail.Version, "npm:") {
+			i := strings.LastIndex(detail.Version, "@")
+			name = detail.Version[4:i]
+			finalVersion = detail.Version[i+1:]
+		}
+
 		// we can't resolve a version from a "file:" dependency
 		if strings.HasPrefix(detail.Version, "file:") {
 			finalVersion = ""
@@ -111,7 +121,12 @@ func parseNpmLockPackages(packages map[string]NpmLockPackage) map[string]Package
 		if namePath == "" {
 			continue
 		}
-		finalName := extractNpmPackageName(namePath)
+
+		finalName := detail.Name
+		if finalName == "" {
+			finalName = extractNpmPackageName(namePath)
+		}
+
 		finalVersion := detail.Version
 
 		commit := tryExtractCommit(detail.Resolved)


Test patch:
diff --git a/.github/workflows/test-action/action.yml b/.github/workflows/test-action/action.yml
index a66778c..e4c611e 100644
--- a/.github/workflows/test-action/action.yml
+++ b/.github/workflows/test-action/action.yml
@@ -20,6 +20,8 @@ runs:
   steps:
     - name: Run go test
       shell: bash
+      env:
+        TEST_ACCEPTANCE: true
       run: ./scripts/run_tests.sh
     - name: Upload coverage to Codecov
       uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
diff --git a/internal/sourceanalysis/rust_test.go b/internal/sourceanalysis/rust_test.go
index 7fce52c..be97cd0 100644
--- a/internal/sourceanalysis/rust_test.go
+++ b/internal/sourceanalysis/rust_test.go
@@ -67,6 +67,7 @@ func Test_functionsFromDWARF(t *testing.T) {
 }
 
 func Test_rustBuildSource(t *testing.T) {
+	testutility.AcceptanceTests(t, "Requires rust toolchain to be installed")
 	t.Parallel()
 
 	workingDir, err := os.Getwd()
diff --git a/internal/testutility/utility.go b/internal/testutility/utility.go
index 9438be2..1501c5e 100644
--- a/internal/testutility/utility.go
+++ b/internal/testutility/utility.go
@@ -99,3 +99,12 @@ func AssertMatchFixtureText(t *testing.T, path string, actual string) {
 		}
 	}
 }
+
+// AcceptanceTests marks this test function as a extended that require additional dependencies
+// automatically skipped unless running in a CI environment
+func AcceptanceTests(t *testing.T, reason string) {
+	t.Helper()
+	if os.Getenv("TEST_ACCEPTANCE") != "true" {
+		t.Skip("Skipping extended test: ", reason)
+	}
+}
diff --git a/pkg/lockfile/parse-npm-lock-v1_test.go b/pkg/lockfile/parse-npm-lock-v1_test.go
index 9b74905..154d79d 100644
--- a/pkg/lockfile/parse-npm-lock-v1_test.go
+++ b/pkg/lockfile/parse-npm-lock-v1_test.go
@@ -178,7 +178,7 @@ func TestParseNpmLock_v1_NestedDependenciesDup(t *testing.T) {
 
 	// todo: convert to using expectPackages w/ listing all expected packages
 	if len(packages) != 39 {
-		t.Errorf("Expected to get two packages, but got %d", len(packages))
+		t.Errorf("Expected to get 39 packages, but got %d", len(packages))
 	}
 
 	expectPackage(t, packages, lockfile.PackageDetails{
@@ -347,3 +347,34 @@ func TestParseNpmLock_v1_Files(t *testing.T) {
 		},
 	})
 }
+
+func TestParseNpmLock_v1_Alias(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseNpmLock("fixtures/npm/alias.v1.json")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "@babel/code-frame",
+			Version:   "7.0.0",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+		{
+			Name:      "string-width",
+			Version:   "4.2.0",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+		{
+			Name:      "string-width",
+			Version:   "5.1.2",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+	})
+}
diff --git a/pkg/lockfile/parse-npm-lock-v2_test.go b/pkg/lockfile/parse-npm-lock-v2_test.go
index b0a4b61..2b0cfb7 100644
--- a/pkg/lockfile/parse-npm-lock-v2_test.go
+++ b/pkg/lockfile/parse-npm-lock-v2_test.go
@@ -336,3 +336,34 @@ func TestParseNpmLock_v2_Files(t *testing.T) {
 		},
 	})
 }
+
+func TestParseNpmLock_v2_Alias(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseNpmLock("fixtures/npm/alias.v2.json")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "@babel/code-frame",
+			Version:   "7.0.0",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+		{
+			Name:      "string-width",
+			Version:   "4.2.0",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+		{
+			Name:      "string-width",
+			Version:   "5.1.2",
+			Ecosystem: lockfile.NpmEcosystem,
+			CompareAs: lockfile.NpmEcosystem,
+		},
+	})
+}