@nodesecure/scanner 4.0.0 → 5.0.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
@@ -1,10 +1,10 @@
1
1
  <img align="center" alt="# Nodesecure Scanner" src="https://user-images.githubusercontent.com/4438263/226018084-113c49e6-6c69-4baa-8f84-87e6d695be6d.jpg">
2
2
 
3
3
  ![version](https://img.shields.io/badge/dynamic/json.svg?style=for-the-badge&url=https://raw.githubusercontent.com/NodeSecure/scanner/master/package.json&query=$.version&label=Version)
4
- [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge)](https://github.com/NodeSecure/scanner/commit-activity)
4
+ [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge)](https://github.com/NodeSecure/scanner/graphs/commit-activity)
5
5
  [![OpenSSF
6
6
  Scorecard](https://api.securityscorecards.dev/projects/github.com/NodeSecure/scanner/badge?style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/NodeSecure/scanner)
7
- [![mit](https://img.shields.io/github/license/Naereen/StrapDown.js.svg?style=for-the-badge)](https://github.com/NodeSecure/scanner/blob/master/LICENSE)
7
+ [![mit](https://img.shields.io/github/license/NodeSecure/scanner.svg?style=for-the-badge)](https://github.com/NodeSecure/scanner/blob/master/LICENSE)
8
8
  ![build](https://img.shields.io/github/actions/workflow/status/NodeSecure/scanner/node.js.yml?style=for-the-badge)
9
9
 
10
10
  ⚡️ Run a static analysis of your module's dependencies.
@@ -0,0 +1,6 @@
1
+ const scanner = {
2
+ disable_scarf: "This dependency could collect data against your consent so think to disable it with the env var: SCARF_ANALYTICS",
3
+ keylogging: "This dependency can retrieve your keyboard and mouse inputs. It can be used for 'keylogging' attacks/malwares."
4
+ };
5
+
6
+ export default { scanner };
package/i18n/french.js ADDED
@@ -0,0 +1,7 @@
1
+ const scanner = {
2
+ disable_scarf: "Cette dépendance peut récolter des données contre votre volonté, pensez donc à la désactiver en fournissant la variable d'environnement SCARF_ANALYTICS",
3
+ keylogging: "Cette dépendance peut obtenir vos entrées clavier ou de souris. Cette dépendance peut être utilisée en tant que 'keylogging' attacks/malwares."
4
+ };
5
+
6
+ export default { scanner };
7
+
package/index.js CHANGED
@@ -38,7 +38,7 @@ export async function cwd(location = process.cwd(), options = {}, logger = new L
38
38
  return depWalker(JSON.parse(str), finalizedOptions, logger);
39
39
  }
40
40
 
41
- export async function from(packageName, options, logger = new Logger()) {
41
+ export async function from(packageName, options = {}, logger = new Logger()) {
42
42
  const registry = options.registry ? new URL(options.registry).toString() : getLocalRegistryURL();
43
43
 
44
44
  logger.start(ScannerLoggerEvents.manifest.fetch);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodesecure/scanner",
3
- "version": "4.0.0",
3
+ "version": "5.0.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": {
@@ -10,11 +10,13 @@
10
10
  "lint": "eslint src test",
11
11
  "prepublishOnly": "pkg-ok",
12
12
  "test": "npm run lint && npm run test-only",
13
- "test-only": "cross-env esm-tape-runner 'test/**/*.spec.js' | tap-monkey",
13
+ "test:ci": "node --test test/**.spec.js test/**/*.spec.js",
14
+ "test-only": "glob -c \"node --test-reporter=spec --test\" \"./test/**/*.spec.js\"",
14
15
  "coverage": "c8 -r html npm run test-only"
15
16
  },
16
17
  "files": [
17
18
  "src",
19
+ "i18n",
18
20
  "types",
19
21
  "index.js",
20
22
  "index.d.ts"
@@ -48,39 +50,35 @@
48
50
  },
49
51
  "homepage": "https://github.com/NodeSecure/scanner#readme",
50
52
  "devDependencies": {
51
- "@nodesecure/eslint-config": "^1.6.0",
53
+ "@nodesecure/eslint-config": "^1.7.0",
52
54
  "@slimio/is": "^2.0.0",
53
- "@small-tech/esm-tape-runner": "^2.0.0",
54
- "@small-tech/tap-monkey": "^1.4.0",
55
- "@types/node": "^18.13.0",
56
- "c8": "^7.12.0",
57
- "cross-env": "^7.0.3",
55
+ "@types/node": "^20.4.5",
56
+ "c8": "^7.13.0",
58
57
  "dotenv": "^16.0.3",
59
- "eslint": "^8.34.0",
58
+ "eslint": "^8.37.0",
60
59
  "get-folder-size": "^4.0.0",
60
+ "glob": "^10.3.4",
61
61
  "pkg-ok": "^3.0.0",
62
- "sinon": "^15.0.1",
63
- "snap-shot-core": "^10.2.4",
64
- "tape": "^5.6.1"
62
+ "sinon": "^15.0.3",
63
+ "snap-shot-core": "^10.2.4"
65
64
  },
66
65
  "dependencies": {
67
- "@nodesecure/authors": "^1.0.1",
66
+ "@nodesecure/authors": "^1.0.2",
68
67
  "@nodesecure/flags": "^2.4.0",
69
68
  "@nodesecure/fs-walk": "^1.0.0",
70
- "@nodesecure/i18n": "^3.0.0",
69
+ "@nodesecure/i18n": "^3.3.0",
71
70
  "@nodesecure/js-x-ray": "^6.0.1",
72
- "@nodesecure/npm-registry-sdk": "^1.4.1",
73
- "@nodesecure/ntlp": "^2.2.0",
74
- "@nodesecure/utils": "^1.0.0",
71
+ "@nodesecure/npm-registry-sdk": "^1.5.2",
72
+ "@nodesecure/ntlp": "^2.2.1",
75
73
  "@nodesecure/vuln": "^1.7.0",
76
74
  "@npm/types": "^1.0.2",
77
- "@npmcli/arborist": "^6.2.2",
75
+ "@npmcli/arborist": "^6.2.6",
78
76
  "@slimio/lock": "^1.0.0",
79
77
  "builtins": "^5.0.1",
80
78
  "combine-async-iterators": "^2.0.1",
81
- "itertools": "^1.7.1",
79
+ "itertools": "^2.1.1",
82
80
  "lodash.difference": "^4.5.0",
83
- "pacote": "^15.0.8",
81
+ "pacote": "^15.1.1",
84
82
  "semver": "^7.3.8"
85
83
  },
86
84
  "type": "module"
@@ -13,11 +13,15 @@ export default class Dependency {
13
13
  this.alias = {};
14
14
 
15
15
  if (parent !== null) {
16
- parent.dependencyCount++;
16
+ parent.addChildren();
17
17
  }
18
18
  this.#parent = parent;
19
19
  }
20
20
 
21
+ addChildren() {
22
+ this.dependencyCount += 1;
23
+ }
24
+
21
25
  get fullName() {
22
26
  return `${this.name} ${this.version}`;
23
27
  }
package/src/depWalker.js CHANGED
@@ -6,7 +6,7 @@ import os from "os";
6
6
 
7
7
  // Import Third-party Dependencies
8
8
  import combineAsyncIterators from "combine-async-iterators";
9
- import iter from "itertools";
9
+ import * as iter from "itertools";
10
10
  import pacote from "pacote";
11
11
  import Arborist from "@npmcli/arborist";
12
12
  import Lock from "@slimio/lock";
@@ -76,6 +76,7 @@ export async function* searchDeepDependencies(packageName, gitURL, options) {
76
76
  }
77
77
 
78
78
  if (exclude.has(cleanName)) {
79
+ current.addChildren();
79
80
  exclude.get(cleanName).add(current.fullName);
80
81
  }
81
82
  else {
@@ -122,6 +123,7 @@ export async function* deepReadEdges(currentPackageName, options) {
122
123
  const cleanName = `${packageName}@${toNode.package.version}`;
123
124
 
124
125
  if (exclude.has(cleanName)) {
126
+ current.addChildren();
125
127
  exclude.get(cleanName).add(current.fullName);
126
128
  }
127
129
  else {
@@ -335,8 +337,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
335
337
  for (const [verStr, verDescriptor] of Object.entries(dependency.versions)) {
336
338
  verDescriptor.flags.push(...addMissingVersionFlags(new Set(verDescriptor.flags), dependency));
337
339
 
338
- const fullName = `${packageName}@${verStr}`;
339
- const usedDeps = exclude.get(fullName) || new Set();
340
+ const usedDeps = exclude.get(`${packageName}@${verStr}`) || new Set();
340
341
  if (usedDeps.size === 0) {
341
342
  continue;
342
343
  }
package/src/manifest.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Import Node.js Dependencies
2
- import fs from "fs/promises";
3
- import path from "path";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
4
 
5
- // Import Third-party Dependencies
6
- import { parseManifestAuthor } from "@nodesecure/utils";
5
+ // Import Internal Dependencies
6
+ import { parseAuthor } from "./utils/index.js";
7
7
 
8
8
  // CONSTANTS
9
9
  // PR welcome to contribute to this list!
@@ -16,8 +16,10 @@ const kNativeNpmPackages = new Set([
16
16
  */
17
17
  const kUnsafeNpmScripts = new Set([
18
18
  "install",
19
- "preinstall", "postinstall",
20
- "preuninstall", "postuninstall"
19
+ "preinstall",
20
+ "postinstall",
21
+ "preuninstall",
22
+ "postuninstall"
21
23
  ]);
22
24
 
23
25
  /**
@@ -48,7 +50,7 @@ export async function readAnalyze(location) {
48
50
  .some((pkg) => kNativeNpmPackages.has(pkg));
49
51
 
50
52
  return {
51
- author: typeof author === "string" ? parseManifestAuthor(author) : author,
53
+ author: parseAuthor(author),
52
54
  description,
53
55
  engines,
54
56
  repository,
@@ -1,11 +1,9 @@
1
1
  // Import Third-party Dependencies
2
2
  import semver from "semver";
3
- import { parseManifestAuthor } from "@nodesecure/utils";
4
3
  import { packument } from "@nodesecure/npm-registry-sdk";
5
4
 
6
- export function parseAuthor(author) {
7
- return typeof author === "string" ? parseManifestAuthor(author) : author;
8
- }
5
+ // Import Internal Dependencies
6
+ import { parseAuthor } from "./utils/index.js";
9
7
 
10
8
  export async function packageMetadata(name, version, options) {
11
9
  const { ref, logger } = options;
@@ -19,7 +17,7 @@ export async function packageMetadata(name, version, options) {
19
17
  const lastVersion = pkg["dist-tags"].latest;
20
18
  const lastUpdateAt = new Date(pkg.time[lastVersion]);
21
19
  const metadata = {
22
- author: parseAuthor(pkg.author) ?? {},
20
+ author: parseAuthor(pkg.author),
23
21
  homepage: pkg.homepage || null,
24
22
  publishedCount: Object.values(pkg.versions).length,
25
23
  lastVersion,
package/src/tarball.js CHANGED
@@ -10,8 +10,13 @@ import ntlp from "@nodesecure/ntlp";
10
10
 
11
11
  // Import Internal Dependencies
12
12
  import {
13
- getTarballComposition, isSensitiveFile, filterDependencyKind, analyzeDependencies, booleanToFlags,
14
- NPM_TOKEN
13
+ getTarballComposition,
14
+ isSensitiveFile,
15
+ filterDependencyKind,
16
+ analyzeDependencies,
17
+ booleanToFlags,
18
+ NPM_TOKEN,
19
+ getSemVerWarning
15
20
  } from "./utils/index.js";
16
21
  import * as manifest from "./manifest.js";
17
22
 
@@ -94,6 +99,10 @@ export async function scanDirOrArchive(name, version, options) {
94
99
 
95
100
  ref.warnings.push(...fileAnalysisResults.flatMap((row) => row.warnings));
96
101
 
102
+ if (/^0(\.\d+)*$/.test(version)) {
103
+ ref.warnings.push(getSemVerWarning(version));
104
+ }
105
+
97
106
  const dependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.dependencies))];
98
107
  const filesDependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.filesDependencies))];
99
108
  const tryDependencies = new Set(fileAnalysisResults.flatMap((row) => row.tryDependencies));
@@ -10,6 +10,7 @@ export * from "./filterDependencyKind.js";
10
10
  export * from "./analyzeDependencies.js";
11
11
  export * from "./booleanToFlags.js";
12
12
  export * from "./addMissingVersionFlags.js";
13
+ export * from "./parseManifestAuthor.js";
13
14
 
14
15
  export const NPM_TOKEN = typeof process.env.NODE_SECURE_TOKEN === "string" ?
15
16
  { token: process.env.NODE_SECURE_TOKEN } :
@@ -0,0 +1,45 @@
1
+ export function manifestAuthorRegex() {
2
+ return /^([^<(]+?)?[ \t]*(?:<([^>(]+?)>)?[ \t]*(?:\(([^)]+?)\)|$)/gm;
3
+ }
4
+
5
+ /**
6
+ * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#people-fields-author-contributors
7
+ */
8
+ export function parseManifestAuthor(manifestAuthorField) {
9
+ if (typeof manifestAuthorField !== "string") {
10
+ throw new TypeError("expected manifestAuthorField to be a string");
11
+ }
12
+
13
+ if (!/\w/.test(manifestAuthorField)) {
14
+ return null;
15
+ }
16
+
17
+ const match = manifestAuthorRegex().exec(manifestAuthorField);
18
+ if (!match) {
19
+ return null;
20
+ }
21
+ const author = {
22
+ name: match[1]
23
+ };
24
+
25
+ for (let id = 2; id < match.length; id++) {
26
+ const val = match[id] || "";
27
+
28
+ if (val.includes("@")) {
29
+ author.email = val;
30
+ }
31
+ else if (val.includes("http")) {
32
+ author.url = val;
33
+ }
34
+ }
35
+
36
+ return author;
37
+ }
38
+
39
+ export function parseAuthor(author) {
40
+ if (typeof author === "string") {
41
+ return parseManifestAuthor(author);
42
+ }
43
+
44
+ return !author || Object.keys(author).length === 0 ? null : author;
45
+ }
@@ -47,3 +47,15 @@ export async function getCleanDependencyName([depName, range]) {
47
47
 
48
48
  return [`${depName}@${range}`, `${depName}@${depVer}`, isLatest];
49
49
  }
50
+
51
+ export function getSemVerWarning(value) {
52
+ return {
53
+ kind: "invalid-semver",
54
+ file: "package.json",
55
+ value,
56
+ location: null,
57
+ i18n: "sast_warnings.invalidSemVer",
58
+ severity: "Information",
59
+ experimental: false
60
+ };
61
+ }
@@ -1,16 +1,26 @@
1
+ // Import Node.js Dependencies
2
+ import path from "node:path";
3
+
1
4
  // Import Third-party Dependencies
2
- import { getToken, taggedString } from "@nodesecure/i18n";
5
+ import * as i18n from "@nodesecure/i18n";
3
6
  import { extractAllAuthors } from "@nodesecure/authors";
4
7
 
8
+ // Import Internal Dependencies
9
+ import { getDirNameFromUrl } from "./dirname.js";
10
+
11
+ i18n.extendFromSystemPath(
12
+ path.join(getDirNameFromUrl(import.meta.url), "..", "..", "i18n")
13
+ );
14
+
5
15
  // CONSTANTS
6
- const kDetectedDep = taggedString`The dependency '${0}' has been detected in the dependency Tree.`;
16
+ const kDetectedDep = i18n.taggedString`The dependency '${0}' has been detected in the dependency Tree.`;
7
17
  const kFlaggedAuthors = [{
8
18
  name: "marak",
9
19
  email: "marak.squires@gmail.com"
10
20
  }];
11
21
  const kDependencyWarnMessage = Object.freeze({
12
- "@scarf/scarf": await getToken("warnings.disable_scarf"),
13
- iohook: await getToken("warnings.keylogging")
22
+ "@scarf/scarf": await i18n.getToken("scanner.disable_scarf"),
23
+ iohook: await i18n.getToken("scanner.keylogging")
14
24
  });
15
25
 
16
26
  /**
@@ -4,11 +4,22 @@ import { license as License } from "@nodesecure/ntlp";
4
4
  import * as Vuln from "@nodesecure/vuln";
5
5
 
6
6
  // Import Third-party Dependencies
7
- import { Maintainer } from "@npm/types";
7
+ import { extractedAuthor } from "@nodesecure/authors";
8
8
 
9
9
  export = Scanner;
10
10
 
11
11
  declare namespace Scanner {
12
+ export interface Author {
13
+ name: string;
14
+ email?: string;
15
+ url?: string;
16
+ }
17
+
18
+ export interface Maintainer {
19
+ name: string;
20
+ email: string;
21
+ }
22
+
12
23
  export interface Publisher {
13
24
  /**
14
25
  * Publisher npm user name.
@@ -45,7 +56,7 @@ declare namespace Scanner {
45
56
  /** Package description */
46
57
  description: string;
47
58
  /** Author of the package. This information is not trustable and can be empty. */
48
- author: Maintainer;
59
+ author: Author | null;
49
60
  engines: {
50
61
  node?: string;
51
62
  npm?: string;
@@ -109,13 +120,13 @@ declare namespace Scanner {
109
120
  hasManyPublishers: boolean;
110
121
  hasReceivedUpdateInOneYear: boolean;
111
122
  /** Author of the package. This information is not trustable and can be empty. */
112
- author: Maintainer;
123
+ author: Author | null;
113
124
  /** Package home page */
114
125
  homepage: string | null;
115
126
  /**
116
127
  * List of maintainers (list of people in the organization related to the package)
117
128
  */
118
- maintainers: { name: string, email: string }[];
129
+ maintainers: Maintainer[];
119
130
  /**
120
131
  * List of people who published this package
121
132
  */