@nodesecure/scanner 5.1.0 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,7 +78,7 @@ interface Options {
78
78
  ## Contributors ✨
79
79
 
80
80
  <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
81
- [![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)
81
+ [![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-)
82
82
  <!-- ALL-CONTRIBUTORS-BADGE:END -->
83
83
 
84
84
  Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -102,6 +102,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
102
102
  <td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/ange-tekeu-a155811b4/"><img src="https://avatars.githubusercontent.com/u/35274201?v=4?s=100" width="100px;" alt="Ange TEKEU"/><br /><sub><b>Ange TEKEU</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=tekeuange23" title="Code">💻</a></td>
103
103
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Kawacrepe"><img src="https://avatars.githubusercontent.com/u/40260517?v=4?s=100" width="100px;" alt="Vincent Dhennin"/><br /><sub><b>Vincent Dhennin</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=Kawacrepe" title="Code">💻</a></td>
104
104
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/fabnguess"><img src="https://avatars.githubusercontent.com/u/72697416?v=4?s=100" width="100px;" alt="Kouadio Fabrice Nguessan"/><br /><sub><b>Kouadio Fabrice Nguessan</b></sub></a><br /><a href="#maintenance-fabnguess" title="Maintenance">🚧</a></td>
105
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/PierreDemailly"><img src="https://avatars.githubusercontent.com/u/39910767?v=4?s=100" width="100px;" alt="PierreDemailly"/><br /><sub><b>PierreDemailly</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=PierreDemailly" title="Code">💻</a> <a href="https://github.com/NodeSecure/scanner/pulls?q=is%3Apr+reviewed-by%3APierreDemailly" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/NodeSecure/scanner/issues?q=author%3APierreDemailly" title="Bug reports">🐛</a> <a href="https://github.com/NodeSecure/scanner/commits?author=PierreDemailly" title="Tests">⚠️</a></td>
105
106
  </tr>
106
107
  </tbody>
107
108
  </table>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodesecure/scanner",
3
- "version": "5.1.0",
3
+ "version": "5.2.1",
4
4
  "description": "A package API to run a static analysis of your module's dependencies.",
5
5
  "exports": "./index.js",
6
6
  "engines": {
@@ -55,7 +55,7 @@
55
55
  "@types/node": "^20.10.0",
56
56
  "c8": "^8.0.1",
57
57
  "dotenv": "^16.3.1",
58
- "eslint": "^8.37.0",
58
+ "eslint": "8.37.0",
59
59
  "get-folder-size": "^4.0.0",
60
60
  "glob": "^10.3.10",
61
61
  "pkg-ok": "^3.0.0",
@@ -66,9 +66,9 @@
66
66
  "@nodesecure/authors": "^1.0.2",
67
67
  "@nodesecure/flags": "^2.4.0",
68
68
  "@nodesecure/fs-walk": "^1.0.0",
69
- "@nodesecure/i18n": "^3.4.0",
70
- "@nodesecure/js-x-ray": "^6.2.0",
71
- "@nodesecure/npm-registry-sdk": "^1.6.1",
69
+ "@nodesecure/i18n": "^3.5.0",
70
+ "@nodesecure/js-x-ray": "^6.3.0",
71
+ "@nodesecure/npm-registry-sdk": "^2.0.0",
72
72
  "@nodesecure/ntlp": "^2.2.1",
73
73
  "@nodesecure/vuln": "^1.7.0",
74
74
  "@npm/types": "^1.0.2",
package/src/depWalker.js CHANGED
@@ -268,7 +268,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
268
268
 
269
269
  if (dependencies.has(name)) {
270
270
  const dep = dependencies.get(name);
271
- promisesToWait.push(manifestMetadata(name, version, dep.metadata));
271
+ promisesToWait.push(manifestMetadata(name, version, dep));
272
272
 
273
273
  const currVersion = Object.keys(current.versions)[0];
274
274
  if (currVersion in dep.versions) {
@@ -293,7 +293,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
293
293
  logger.tick(ScannerLoggerEvents.analysis.tree);
294
294
 
295
295
  // There is no need to fetch 'N' times the npm metadata for the same package.
296
- if (fetchedMetadataPackages.has(name)) {
296
+ if (fetchedMetadataPackages.has(name) || !current.versions[version].existOnRemoteRegistry) {
297
297
  logger.tick(ScannerLoggerEvents.analysis.registry);
298
298
  }
299
299
  else {
@@ -340,6 +340,13 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
340
340
 
341
341
  for (const [version, integrity] of Object.entries(metadataIntegrities)) {
342
342
  const dependencyVer = dependency.versions[version];
343
+
344
+ const isEmptyPackage = dependencyVer.warnings
345
+ .some((warning) => warning.kind === "empty-package");
346
+ if (isEmptyPackage) {
347
+ globalWarnings.push(`${packageName}@${version} only contain a package.json file!`);
348
+ }
349
+
343
350
  if (!("integrity" in dependencyVer) || dependencyVer.flags.includes("isGit")) {
344
351
  continue;
345
352
  }
package/src/manifest.js CHANGED
@@ -54,7 +54,9 @@ export async function readAnalyze(location) {
54
54
  } = await read(location);
55
55
 
56
56
  for (const [scriptName, scriptValue] of Object.entries(scripts)) {
57
- scripts[scriptName] = scriptValue.replaceAll(kNodemodulesBinPrefix, "");
57
+ if (scriptValue.startsWith(kNodemodulesBinPrefix)) {
58
+ scripts[scriptName] = scriptValue.replaceAll(kNodemodulesBinPrefix, "");
59
+ }
58
60
  }
59
61
 
60
62
  const integrityObj = {
@@ -6,14 +6,25 @@ import semver from "semver";
6
6
  import { packument, packumentVersion } from "@nodesecure/npm-registry-sdk";
7
7
 
8
8
  // Import Internal Dependencies
9
- import { parseAuthor } from "./utils/index.js";
9
+ import { parseAuthor, getLinks } from "./utils/index.js";
10
10
 
11
- export async function manifestMetadata(name, version, metadata) {
11
+ export async function manifestMetadata(
12
+ name,
13
+ version,
14
+ dependency
15
+ ) {
12
16
  try {
13
17
  const pkgVersion = await packumentVersion(name, version);
14
18
 
15
19
  const integrity = getPackumentVersionIntegrity(pkgVersion);
16
- metadata.integrity[version] = integrity;
20
+ Object.assign(
21
+ dependency.versions[version],
22
+ {
23
+ links: getLinks(pkgVersion)
24
+ }
25
+ );
26
+
27
+ dependency.metadata.integrity[version] = integrity;
17
28
  }
18
29
  catch {
19
30
  // Ignore
@@ -45,8 +56,9 @@ export async function packageMetadata(name, version, options) {
45
56
  };
46
57
 
47
58
  const isOutdated = semver.neq(version, lastVersion);
59
+ const flags = ref.versions[version].flags;
48
60
  if (isOutdated) {
49
- ref.versions[version].flags.push("isOutdated");
61
+ flags.push("isOutdated");
50
62
  }
51
63
 
52
64
  const publishers = new Set();
@@ -54,12 +66,17 @@ export async function packageMetadata(name, version, options) {
54
66
  for (const ver of Object.values(pkg.versions).reverse()) {
55
67
  const versionSpec = `${ver.name}:${ver.version}`;
56
68
  if (packageSpec === versionSpec) {
69
+ if (ver.deprecated && !flags.includes("isDeprecated")) {
70
+ flags.push("isDeprecated");
71
+ }
72
+
57
73
  metadata.integrity[ver.version] = getPackumentVersionIntegrity(
58
74
  ver
59
75
  );
60
76
  }
61
77
 
62
78
  const { _npmUser: npmUser, version, maintainers = [] } = ver;
79
+
63
80
  const isNullOrUndefined = typeof npmUser === "undefined" || npmUser === null;
64
81
  if (isNullOrUndefined || !("name" in npmUser) || typeof npmUser.name !== "string") {
65
82
  continue;
@@ -85,6 +102,7 @@ export async function packageMetadata(name, version, options) {
85
102
  }
86
103
  }
87
104
 
105
+ Object.assign(ref.versions[version], { links: getLinks(pkg.versions[version]) });
88
106
  Object.assign(ref.metadata, metadata);
89
107
  }
90
108
  catch {
package/src/tarball.js CHANGED
@@ -67,6 +67,10 @@ export async function scanDirOrArchive(name, version, options) {
67
67
  });
68
68
  await timers.setImmediate();
69
69
  }
70
+ else {
71
+ // Set links to an empty object because theses are generated only for NPM tarballs
72
+ Object.assign(ref, { links: {} });
73
+ }
70
74
 
71
75
  // Read the package.json at the root of the directory or archive.
72
76
  const {
@@ -88,6 +92,17 @@ export async function scanDirOrArchive(name, version, options) {
88
92
 
89
93
  // Get the composition of the (extracted) directory
90
94
  const { ext, files, size } = await getTarballComposition(dest);
95
+ if (files.length === 1 && files.includes("package.json")) {
96
+ ref.warnings.push({
97
+ kind: "empty-package",
98
+ location: null,
99
+ i18n: "sast_warnings.emptyPackage",
100
+ severity: "Critical",
101
+ source: "Scanner",
102
+ experimental: false
103
+ });
104
+ }
105
+
91
106
  ref.size = size;
92
107
  ref.composition.extensions.push(...ext);
93
108
  ref.composition.files.push(...files);
@@ -0,0 +1,36 @@
1
+ // CONSTANTS
2
+ const kVCSHosts = new Set(["github.com", "gitlab.com"]);
3
+
4
+ function getVCSRepositoryURL(link) {
5
+ try {
6
+ const url = new URL(link);
7
+ const { hostname, pathname } = url;
8
+
9
+ if (kVCSHosts.has(hostname) === false) {
10
+ return null;
11
+ }
12
+
13
+ const [owner, repo] = pathname.split("/").filter(Boolean).map((curr) => curr.replace(".git", ""));
14
+
15
+ return `https://${hostname}/${owner}/${repo}`;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * @param {import("@nodesecure/npm-registry-sdk").PackumentVersion} packumentVersion
24
+ */
25
+ export function getLinks(
26
+ packumentVersion
27
+ ) {
28
+ const homepage = packumentVersion.homepage || null;
29
+ const repositoryUrl = packumentVersion.repository?.url || null;
30
+
31
+ return {
32
+ npm: `https://www.npmjs.com/package/${packumentVersion.name}/v/${packumentVersion.version}`,
33
+ homepage,
34
+ repository: getVCSRepositoryURL(homepage) ?? getVCSRepositoryURL(repositoryUrl)
35
+ };
36
+ }
@@ -11,6 +11,7 @@ export * from "./analyzeDependencies.js";
11
11
  export * from "./booleanToFlags.js";
12
12
  export * from "./addMissingVersionFlags.js";
13
13
  export * from "./parseManifestAuthor.js";
14
+ export * from "./getLinks.js";
14
15
 
15
16
  export const NPM_TOKEN = typeof process.env.NODE_SECURE_TOKEN === "string" ?
16
17
  { token: process.env.NODE_SECURE_TOKEN } :
@@ -40,6 +40,15 @@ declare namespace Scanner {
40
40
  at: string;
41
41
  }
42
42
 
43
+ export interface DependencyLinks {
44
+ /** NPM Registry page */
45
+ npm: string;
46
+ /** Homepage URL */
47
+ homepage?: string;
48
+ /** VCS repository URL */
49
+ repository?: string;
50
+ }
51
+
43
52
  export interface DependencyVersion {
44
53
  /** Id of the package (useful for usedBy relation) */
45
54
  id: number;
@@ -111,6 +120,7 @@ declare namespace Scanner {
111
120
  * (Not supported on GIT dependency)
112
121
  */
113
122
  integrity?: string;
123
+ links: DependencyLinks;
114
124
  }
115
125
 
116
126
  export interface Dependency {