@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 +2 -2
- package/i18n/english.js +6 -0
- package/i18n/french.js +7 -0
- package/index.js +1 -1
- package/package.json +18 -20
- package/src/class/dependency.class.js +5 -1
- package/src/depWalker.js +4 -3
- package/src/manifest.js +9 -7
- package/src/npmRegistry.js +3 -5
- package/src/tarball.js +11 -2
- package/src/utils/index.js +1 -0
- package/src/utils/parseManifestAuthor.js +45 -0
- package/src/utils/semver.js +12 -0
- package/src/utils/warnings.js +14 -4
- package/types/scanner.d.ts +15 -4
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
|

|
|
4
|
-
[](https://github.com/NodeSecure/scanner/commit-activity)
|
|
4
|
+
[](https://github.com/NodeSecure/scanner/graphs/commit-activity)
|
|
5
5
|
[](https://api.securityscorecards.dev/projects/github.com/NodeSecure/scanner)
|
|
7
|
-
[](https://github.com/NodeSecure/scanner/blob/master/LICENSE)
|
|
8
8
|

|
|
9
9
|
|
|
10
10
|
⚡️ Run a static analysis of your module's dependencies.
|
package/i18n/english.js
ADDED
|
@@ -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": "
|
|
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
|
|
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.
|
|
53
|
+
"@nodesecure/eslint-config": "^1.7.0",
|
|
52
54
|
"@slimio/is": "^2.0.0",
|
|
53
|
-
"@
|
|
54
|
-
"
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
69
|
+
"@nodesecure/i18n": "^3.3.0",
|
|
71
70
|
"@nodesecure/js-x-ray": "^6.0.1",
|
|
72
|
-
"@nodesecure/npm-registry-sdk": "^1.
|
|
73
|
-
"@nodesecure/ntlp": "^2.2.
|
|
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.
|
|
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.
|
|
79
|
+
"itertools": "^2.1.1",
|
|
82
80
|
"lodash.difference": "^4.5.0",
|
|
83
|
-
"pacote": "^15.
|
|
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.
|
|
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
|
|
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
|
|
6
|
-
import {
|
|
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",
|
|
20
|
-
"
|
|
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:
|
|
53
|
+
author: parseAuthor(author),
|
|
52
54
|
description,
|
|
53
55
|
engines,
|
|
54
56
|
repository,
|
package/src/npmRegistry.js
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
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,
|
|
14
|
-
|
|
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));
|
package/src/utils/index.js
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/semver.js
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/warnings.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
1
4
|
// Import Third-party Dependencies
|
|
2
|
-
import
|
|
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("
|
|
13
|
-
iohook: await getToken("
|
|
22
|
+
"@scarf/scarf": await i18n.getToken("scanner.disable_scarf"),
|
|
23
|
+
iohook: await i18n.getToken("scanner.keylogging")
|
|
14
24
|
});
|
|
15
25
|
|
|
16
26
|
/**
|
package/types/scanner.d.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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:
|
|
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:
|
|
129
|
+
maintainers: Maintainer[];
|
|
119
130
|
/**
|
|
120
131
|
* List of people who published this package
|
|
121
132
|
*/
|