@nodesecure/scanner 2.1.0 → 3.1.1-rc.0
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/LICENSE +1 -1
- package/README.md +2 -1
- package/index.d.ts +2 -2
- package/index.js +10 -9
- package/package.json +18 -35
- package/src/{dependency.class.js → class/dependency.class.js} +22 -21
- package/src/{logger.class.js → class/logger.class.js} +0 -0
- package/src/constants.js +13 -0
- package/src/depWalker.js +66 -132
- package/src/manifest.js +57 -0
- package/src/npmRegistry.js +68 -0
- package/src/tarball.js +85 -189
- package/src/utils/addMissingVersionFlags.js +24 -0
- package/src/utils/analyzeDependencies.js +40 -0
- package/src/utils/booleanToFlags.js +12 -0
- package/src/utils/filterDependencyKind.js +44 -0
- package/src/utils/getPackageName.js +18 -2
- package/src/utils/getTarballComposition.js +8 -10
- package/src/utils/index.js +8 -6
- package/src/utils/isGitDependency.js +20 -0
- package/src/utils/isSensitiveFile.js +6 -0
- package/src/utils/mergeDependencies.js +4 -1
- package/src/utils/semver.js +3 -3
- package/types/api.d.ts +5 -2
- package/types/logger.d.ts +17 -1
- package/types/scanner.d.ts +133 -34
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2021 NodeSecure
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ interface Options {
|
|
|
69
69
|
## Contributors ✨
|
|
70
70
|
|
|
71
71
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
72
|
-
[](#contributors-)
|
|
73
73
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
74
74
|
|
|
75
75
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
@@ -82,6 +82,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|
|
82
82
|
<td align="center"><a href="https://www.linkedin.com/in/thomas-gentilhomme/"><img src="https://avatars.githubusercontent.com/u/4438263?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gentilhomme</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=fraxken" title="Code">💻</a> <a href="https://github.com/NodeSecure/scanner/commits?author=fraxken" title="Documentation">📖</a> <a href="https://github.com/NodeSecure/scanner/pulls?q=is%3Apr+reviewed-by%3Afraxken" title="Reviewed Pull Requests">👀</a> <a href="#security-fraxken" title="Security">🛡️</a> <a href="https://github.com/NodeSecure/scanner/issues?q=author%3Afraxken" title="Bug reports">🐛</a></td>
|
|
83
83
|
<td align="center"><a href="http://tonygo.dev"><img src="https://avatars.githubusercontent.com/u/22824417?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tony Gorez</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=tony-go" title="Code">💻</a> <a href="https://github.com/NodeSecure/scanner/commits?author=tony-go" title="Documentation">📖</a> <a href="https://github.com/NodeSecure/scanner/pulls?q=is%3Apr+reviewed-by%3Atony-go" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/NodeSecure/scanner/issues?q=author%3Atony-go" title="Bug reports">🐛</a></td>
|
|
84
84
|
<td align="center"><a href="https://mickaelcroquet.fr"><img src="https://avatars.githubusercontent.com/u/23740372?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Haze</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=CroquetMickael" title="Code">💻</a></td>
|
|
85
|
+
<td align="center"><a href="https://github.com/mbalabash"><img src="https://avatars.githubusercontent.com/u/16868922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maksim Balabash</b></sub></a><br /><a href="https://github.com/NodeSecure/scanner/commits?author=mbalabash" title="Code">💻</a></td>
|
|
85
86
|
</tr>
|
|
86
87
|
</table>
|
|
87
88
|
|
package/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import Scanner from "./types/scanner";
|
|
2
|
-
import { cwd, from, verify } from "./types/api";
|
|
2
|
+
import { cwd, from, verify, ScannerLoggerEvents } from "./types/api";
|
|
3
3
|
import { depWalker } from "./types/walker";
|
|
4
4
|
import { Logger, LoggerEventData } from "./types/logger";
|
|
5
5
|
import tarball from "./types/tarball";
|
|
6
6
|
|
|
7
7
|
export {
|
|
8
|
-
cwd, from, verify,
|
|
8
|
+
cwd, from, verify, ScannerLoggerEvents,
|
|
9
9
|
Scanner,
|
|
10
10
|
Logger,
|
|
11
11
|
LoggerEventData,
|
package/index.js
CHANGED
|
@@ -10,8 +10,9 @@ import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
|
10
10
|
|
|
11
11
|
// Import Internal Dependencies
|
|
12
12
|
import { depWalker } from "./src/depWalker.js";
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
13
|
+
import { NPM_TOKEN } from "./src/utils/index.js";
|
|
14
|
+
import { ScannerLoggerEvents } from "./src/constants.js";
|
|
15
|
+
import Logger from "./src/class/logger.class.js";
|
|
15
16
|
import * as tarball from "./src/tarball.js";
|
|
16
17
|
|
|
17
18
|
// CONSTANTS
|
|
@@ -20,20 +21,20 @@ const kDefaultCwdOptions = { forceRootAnalysis: true, usePackageLock: true };
|
|
|
20
21
|
export async function cwd(cwd = process.cwd(), options = {}, logger = new Logger()) {
|
|
21
22
|
const finalizedOptions = Object.assign({}, kDefaultCwdOptions, options);
|
|
22
23
|
|
|
23
|
-
logger.start(
|
|
24
|
+
logger.start(ScannerLoggerEvents.manifest.read);
|
|
24
25
|
const packagePath = path.join(cwd, "package.json");
|
|
25
26
|
const str = await fs.readFile(packagePath, "utf-8");
|
|
26
|
-
logger.end(
|
|
27
|
+
logger.end(ScannerLoggerEvents.manifest.read);
|
|
27
28
|
|
|
28
29
|
return depWalker(JSON.parse(str), finalizedOptions, logger);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export async function from(packageName, options, logger = new Logger()) {
|
|
32
|
-
logger.start(
|
|
33
|
+
logger.start(ScannerLoggerEvents.manifest.fetch);
|
|
33
34
|
const manifest = await pacote.manifest(packageName, {
|
|
34
|
-
...
|
|
35
|
+
...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
|
|
35
36
|
});
|
|
36
|
-
logger.end(
|
|
37
|
+
logger.end(ScannerLoggerEvents.manifest.fetch);
|
|
37
38
|
|
|
38
39
|
return depWalker(manifest, options, logger);
|
|
39
40
|
}
|
|
@@ -48,7 +49,7 @@ export async function verify(packageName = null) {
|
|
|
48
49
|
|
|
49
50
|
try {
|
|
50
51
|
await pacote.extract(packageName, dest, {
|
|
51
|
-
...
|
|
52
|
+
...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
return await tarball.scanPackage(dest, packageName);
|
|
@@ -59,4 +60,4 @@ export async function verify(packageName = null) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
export { depWalker, tarball, Logger };
|
|
63
|
+
export { depWalker, tarball, Logger, ScannerLoggerEvents };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodesecure/scanner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.1-rc.0",
|
|
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,7 +10,8 @@
|
|
|
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
|
|
13
|
+
"test-only": "cross-env esm-tape-runner 'test/**/*.spec.js' | tap-monkey",
|
|
14
|
+
"coverage": "c8 -r html npm run test-only"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"src",
|
|
@@ -46,54 +47,36 @@
|
|
|
46
47
|
"url": "https://github.com/NodeSecure/scanner/issues"
|
|
47
48
|
},
|
|
48
49
|
"homepage": "https://github.com/NodeSecure/scanner#readme",
|
|
49
|
-
"jest": {
|
|
50
|
-
"setupFilesAfterEnv": [
|
|
51
|
-
"./jest.setup.js"
|
|
52
|
-
],
|
|
53
|
-
"collectCoverage": true,
|
|
54
|
-
"collectCoverageFrom": [
|
|
55
|
-
"**/src/**/*.js"
|
|
56
|
-
],
|
|
57
|
-
"testEnvironment": "node",
|
|
58
|
-
"testMatch": [
|
|
59
|
-
"**/test/**/*.js"
|
|
60
|
-
],
|
|
61
|
-
"testPathIgnorePatterns": [
|
|
62
|
-
"/test/fixtures/"
|
|
63
|
-
],
|
|
64
|
-
"moduleNameMapper": {
|
|
65
|
-
"^@nodesecure/npm-registry-sdk$": "@nodesecure/npm-registry-sdk/dist/index.js",
|
|
66
|
-
"^@nodesecure/sec-literal$": "@nodesecure/sec-literal/src/index.js",
|
|
67
|
-
"^estree-walker$": "estree-walker/src/index.js"
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
50
|
"devDependencies": {
|
|
71
51
|
"@nodesecure/eslint-config": "^1.3.0",
|
|
72
52
|
"@slimio/is": "^1.5.1",
|
|
73
|
-
"@
|
|
74
|
-
"@
|
|
53
|
+
"@small-tech/esm-tape-runner": "^1.0.3",
|
|
54
|
+
"@small-tech/tap-monkey": "^1.3.0",
|
|
55
|
+
"@types/node": "^16.11.10",
|
|
56
|
+
"c8": "^7.10.0",
|
|
75
57
|
"cross-env": "^7.0.3",
|
|
76
58
|
"dotenv": "^10.0.0",
|
|
77
|
-
"eslint": "^8.
|
|
59
|
+
"eslint": "^8.3.0",
|
|
78
60
|
"get-folder-size": "^3.1.0",
|
|
79
|
-
"
|
|
80
|
-
"
|
|
61
|
+
"pkg-ok": "^2.3.1",
|
|
62
|
+
"sinon": "^12.0.1",
|
|
63
|
+
"snap-shot-core": "^10.2.4",
|
|
64
|
+
"tape": "^5.3.2"
|
|
81
65
|
},
|
|
82
66
|
"dependencies": {
|
|
83
|
-
"@nodesecure/flags": "^2.
|
|
67
|
+
"@nodesecure/flags": "^2.2.0",
|
|
84
68
|
"@nodesecure/fs-walk": "^1.0.0",
|
|
85
|
-
"@nodesecure/i18n": "^1.2.
|
|
86
|
-
"@nodesecure/js-x-ray": "^4.0
|
|
69
|
+
"@nodesecure/i18n": "^1.2.1",
|
|
70
|
+
"@nodesecure/js-x-ray": "^4.2.0",
|
|
87
71
|
"@nodesecure/npm-registry-sdk": "^1.3.0",
|
|
88
|
-
"@nodesecure/ntlp": "^2.
|
|
72
|
+
"@nodesecure/ntlp": "^2.1.0",
|
|
89
73
|
"@nodesecure/utils": "^1.0.0",
|
|
90
|
-
"@nodesecure/vuln": "^1.4.
|
|
74
|
+
"@nodesecure/vuln": "^1.4.1",
|
|
91
75
|
"@npm/types": "^1.0.1",
|
|
92
|
-
"@npmcli/arborist": "^4.0
|
|
76
|
+
"@npmcli/arborist": "^4.1.0",
|
|
93
77
|
"@slimio/lock": "^1.0.0",
|
|
94
78
|
"builtins": "^4.0.0",
|
|
95
79
|
"combine-async-iterators": "^2.0.1",
|
|
96
|
-
"is-minified-code": "^2.0.0",
|
|
97
80
|
"itertools": "^1.7.1",
|
|
98
81
|
"lodash.difference": "^4.5.0",
|
|
99
82
|
"pacote": "^12.0.2",
|
|
@@ -56,28 +56,29 @@ export default class Dependency {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
versions: {
|
|
60
|
+
[this.version]: {
|
|
61
|
+
id: typeof customId === "number" ? customId : Dependency.currentId++,
|
|
62
|
+
usedBy: this.parent,
|
|
63
|
+
flags: this.flags,
|
|
64
|
+
description: "",
|
|
65
|
+
size: 0,
|
|
66
|
+
author: {},
|
|
67
|
+
warnings: this.warnings,
|
|
68
|
+
composition: {
|
|
69
|
+
extensions: [],
|
|
70
|
+
files: [],
|
|
71
|
+
minified: [],
|
|
72
|
+
unused: [],
|
|
73
|
+
missing: [],
|
|
74
|
+
required_files: [],
|
|
75
|
+
required_nodejs: [],
|
|
76
|
+
required_thirdparty: []
|
|
77
|
+
},
|
|
78
|
+
license: "unkown license",
|
|
79
|
+
gitUrl: this.gitUrl
|
|
80
|
+
}
|
|
79
81
|
},
|
|
80
|
-
versions: [this.version],
|
|
81
82
|
vulnerabilities: [],
|
|
82
83
|
metadata: {
|
|
83
84
|
dependencyCount: this.dependencyCount,
|
|
File without changes
|
package/src/constants.js
ADDED
package/src/depWalker.js
CHANGED
|
@@ -8,18 +8,21 @@ import os from "os";
|
|
|
8
8
|
import combineAsyncIterators from "combine-async-iterators";
|
|
9
9
|
import iter from "itertools";
|
|
10
10
|
import pacote from "pacote";
|
|
11
|
-
import semver from "semver";
|
|
12
11
|
import Arborist from "@npmcli/arborist";
|
|
13
12
|
import Lock from "@slimio/lock";
|
|
14
|
-
import { packument, getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
15
13
|
import * as vuln from "@nodesecure/vuln";
|
|
16
|
-
import {
|
|
14
|
+
import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
15
|
+
import { ScannerLoggerEvents } from "./constants.js";
|
|
17
16
|
|
|
18
17
|
// Import Internal Dependencies
|
|
19
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
mergeDependencies, getCleanDependencyName, getDependenciesWarnings, addMissingVersionFlags, isGitDependency,
|
|
20
|
+
NPM_TOKEN
|
|
21
|
+
} from "./utils/index.js";
|
|
20
22
|
import { scanDirOrArchive } from "./tarball.js";
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
+
import { packageMetadata } from "./npmRegistry.js";
|
|
24
|
+
import Dependency from "./class/dependency.class.js";
|
|
25
|
+
import Logger from "./class/logger.class.js";
|
|
23
26
|
|
|
24
27
|
const { version: packageVersion } = JSON.parse(
|
|
25
28
|
readFileSync(
|
|
@@ -27,20 +30,18 @@ const { version: packageVersion } = JSON.parse(
|
|
|
27
30
|
)
|
|
28
31
|
);
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
32
|
-
const isGit = typeof gitURL === "string";
|
|
33
|
+
export async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
33
34
|
const { exclude, currDepth = 0, parent, maxDepth } = options;
|
|
34
35
|
|
|
35
|
-
const { name, version, deprecated, ...pkg } = await pacote.manifest(
|
|
36
|
-
...
|
|
36
|
+
const { name, version, deprecated, ...pkg } = await pacote.manifest(gitURL ?? packageName, {
|
|
37
|
+
...NPM_TOKEN,
|
|
37
38
|
registry: getLocalRegistryURL(),
|
|
38
39
|
cache: `${os.homedir()}/.npm`
|
|
39
40
|
});
|
|
40
41
|
const { dependencies, customResolvers } = mergeDependencies(pkg);
|
|
41
42
|
|
|
42
43
|
const current = new Dependency(name, version, parent);
|
|
43
|
-
|
|
44
|
+
gitURL !== null && current.isGit(gitURL);
|
|
44
45
|
current.addFlag("isDeprecated", deprecated === true);
|
|
45
46
|
current.addFlag("hasCustomResolver", customResolvers.size > 0);
|
|
46
47
|
current.addFlag("hasDependencies", dependencies.size > 0);
|
|
@@ -50,9 +51,9 @@ async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
|
50
51
|
exclude, currDepth: currDepth + 1, parent: current, maxDepth
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
const gitDependencies = iter.filter(customResolvers.entries(), ([, valueStr]) => valueStr
|
|
54
|
+
const gitDependencies = iter.filter(customResolvers.entries(), ([, valueStr]) => isGitDependency(valueStr));
|
|
54
55
|
for (const [depName, valueStr] of gitDependencies) {
|
|
55
|
-
yield* searchDeepDependencies(depName, valueStr
|
|
56
|
+
yield* searchDeepDependencies(depName, valueStr, config);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
const depsNames = await Promise.all(iter.map(dependencies.entries(), getCleanDependencyName));
|
|
@@ -66,7 +67,7 @@ async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
|
66
67
|
}
|
|
67
68
|
else {
|
|
68
69
|
exclude.set(cleanName, new Set([current.fullName]));
|
|
69
|
-
yield* searchDeepDependencies(fullName,
|
|
70
|
+
yield* searchDeepDependencies(fullName, null, config);
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -74,7 +75,7 @@ async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
|
74
75
|
yield current;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLockMode }) {
|
|
78
|
+
export async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLockMode }) {
|
|
78
79
|
const { version, integrity = to.integrity } = to.package;
|
|
79
80
|
|
|
80
81
|
const updatedVersion = version === "*" || typeof version === "undefined" ? "latest" : version;
|
|
@@ -82,7 +83,7 @@ async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLoc
|
|
|
82
83
|
|
|
83
84
|
if (fullLockMode) {
|
|
84
85
|
const { deprecated, _integrity, ...pkg } = await pacote.manifest(`${currentPackageName}@${updatedVersion}`, {
|
|
85
|
-
...
|
|
86
|
+
...NPM_TOKEN,
|
|
86
87
|
registry: getLocalRegistryURL(),
|
|
87
88
|
cache: `${os.homedir()}/.npm`
|
|
88
89
|
});
|
|
@@ -111,64 +112,7 @@ async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLoc
|
|
|
111
112
|
yield current;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
async function
|
|
115
|
-
const { ref, locker } = options;
|
|
116
|
-
const free = await locker.acquireOne();
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const pkg = await packument(name);
|
|
120
|
-
|
|
121
|
-
const publishers = new Set();
|
|
122
|
-
const oneYearFromToday = new Date();
|
|
123
|
-
oneYearFromToday.setFullYear(oneYearFromToday.getFullYear() - 1);
|
|
124
|
-
|
|
125
|
-
ref.metadata.lastVersion = pkg["dist-tags"].latest;
|
|
126
|
-
if (semver.neq(version, ref.metadata.lastVersion)) {
|
|
127
|
-
ref[version].flags.push("isOutdated");
|
|
128
|
-
}
|
|
129
|
-
ref.metadata.publishedCount = Object.values(pkg.versions).length;
|
|
130
|
-
ref.metadata.lastUpdateAt = new Date(pkg.time[ref.metadata.lastVersion]);
|
|
131
|
-
ref.metadata.hasReceivedUpdateInOneYear = !(oneYearFromToday > ref.metadata.lastUpdateAt);
|
|
132
|
-
ref.metadata.homepage = pkg.homepage || null;
|
|
133
|
-
ref.metadata.maintainers = pkg.maintainers;
|
|
134
|
-
if (typeof pkg.author === "string") {
|
|
135
|
-
ref.metadata.author = parseManifestAuthor(pkg.author);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
ref.metadata.author = pkg.author;
|
|
139
|
-
}
|
|
140
|
-
const authorName = ref.metadata.author?.name ?? null;
|
|
141
|
-
|
|
142
|
-
for (const ver of Object.values(pkg.versions)) {
|
|
143
|
-
const { _npmUser: npmUser, version } = ver;
|
|
144
|
-
|
|
145
|
-
const isNullOrUndefined = typeof npmUser === "undefined" || npmUser === null;
|
|
146
|
-
if (isNullOrUndefined || !("name" in npmUser) || typeof npmUser.name !== "string") {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (authorName === null) {
|
|
151
|
-
ref.metadata.author.name = npmUser.name;
|
|
152
|
-
}
|
|
153
|
-
else if (npmUser.name !== ref.metadata.author.name) {
|
|
154
|
-
ref.metadata.hasManyPublishers = true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!publishers.has(npmUser.name)) {
|
|
158
|
-
publishers.add(npmUser.name);
|
|
159
|
-
ref.metadata.publishers.push({ name: npmUser.name, version, at: new Date(pkg.time[version]) });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch (err) {
|
|
164
|
-
// Ignore
|
|
165
|
-
}
|
|
166
|
-
finally {
|
|
167
|
-
free();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function* getRootDependencies(manifest, options) {
|
|
115
|
+
export async function* getRootDependencies(manifest, options) {
|
|
172
116
|
const { maxDepth = 4, exclude, usePackageLock, fullLockMode } = options;
|
|
173
117
|
|
|
174
118
|
const { dependencies, customResolvers } = mergeDependencies(manifest, void 0);
|
|
@@ -179,7 +123,7 @@ async function* getRootDependencies(manifest, options) {
|
|
|
179
123
|
let iterators;
|
|
180
124
|
if (usePackageLock) {
|
|
181
125
|
const arb = new Arborist({
|
|
182
|
-
...
|
|
126
|
+
...NPM_TOKEN,
|
|
183
127
|
registry: getLocalRegistryURL()
|
|
184
128
|
});
|
|
185
129
|
let tree;
|
|
@@ -191,15 +135,15 @@ async function* getRootDependencies(manifest, options) {
|
|
|
191
135
|
tree = await arb.loadVirtual();
|
|
192
136
|
}
|
|
193
137
|
|
|
194
|
-
iterators = iter.filter(tree.edgesOut.entries(), ([, { to }]) => !to.dev)
|
|
138
|
+
iterators = iter.filter(tree.edgesOut.entries(), ([, { to }]) => to !== null && !to.dev)
|
|
195
139
|
.map(([packageName, { to }]) => deepReadEdges(packageName, { to, parent, fullLockMode, exclude }));
|
|
196
140
|
}
|
|
197
141
|
else {
|
|
198
142
|
const configRef = { exclude, maxDepth, parent };
|
|
199
143
|
iterators = [
|
|
200
|
-
...iter.filter(customResolvers.entries(), ([, valueStr]) => valueStr
|
|
201
|
-
.map(([depName, valueStr]) => searchDeepDependencies(depName, valueStr
|
|
202
|
-
...iter.map(dependencies.entries(), ([name, ver]) => searchDeepDependencies(`${name}@${ver}`,
|
|
144
|
+
...iter.filter(customResolvers.entries(), ([, valueStr]) => isGitDependency(valueStr))
|
|
145
|
+
.map(([depName, valueStr]) => searchDeepDependencies(depName, valueStr, configRef)),
|
|
146
|
+
...iter.map(dependencies.entries(), ([name, ver]) => searchDeepDependencies(`${name}@${ver}`, null, configRef))
|
|
203
147
|
];
|
|
204
148
|
}
|
|
205
149
|
for await (const dep of combineAsyncIterators({}, ...iterators)) {
|
|
@@ -221,26 +165,6 @@ async function* getRootDependencies(manifest, options) {
|
|
|
221
165
|
yield parent;
|
|
222
166
|
}
|
|
223
167
|
|
|
224
|
-
function* addMissingVersionFlags(flags, descriptor) {
|
|
225
|
-
const { metadata, vulnerabilities = [], versions } = descriptor;
|
|
226
|
-
|
|
227
|
-
if (!metadata.hasReceivedUpdateInOneYear && flags.has("hasOutdatedDependency") && !flags.has("isDead")) {
|
|
228
|
-
yield "isDead";
|
|
229
|
-
}
|
|
230
|
-
if (metadata.hasManyPublishers && !flags.has("hasManyPublishers")) {
|
|
231
|
-
yield "hasManyPublishers";
|
|
232
|
-
}
|
|
233
|
-
if (metadata.hasChangedAuthor && !flags.has("hasChangedAuthor")) {
|
|
234
|
-
yield "hasChangedAuthor";
|
|
235
|
-
}
|
|
236
|
-
if (vulnerabilities.length > 0 && !flags.has("hasVulnerabilities")) {
|
|
237
|
-
yield "hasVulnerabilities";
|
|
238
|
-
}
|
|
239
|
-
if (versions.length > 1 && !flags.has("hasDuplicate")) {
|
|
240
|
-
yield "hasDuplicate";
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
168
|
/**
|
|
245
169
|
* @param {*} manifest
|
|
246
170
|
* @param {*} options
|
|
@@ -260,59 +184,68 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
|
|
|
260
184
|
|
|
261
185
|
const payload = {
|
|
262
186
|
id: tmpLocation.slice(-6),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
187
|
+
rootDependencyName: manifest.name,
|
|
188
|
+
scannerVersion: packageVersion,
|
|
189
|
+
vulnerabilityStrategy,
|
|
190
|
+
warnings: []
|
|
267
191
|
};
|
|
268
192
|
|
|
269
193
|
// We are dealing with an exclude Map to avoid checking a package more than one time in searchDeepDependencies
|
|
270
194
|
const exclude = new Map();
|
|
195
|
+
const dependencies = new Map();
|
|
271
196
|
|
|
272
197
|
{
|
|
273
|
-
logger
|
|
198
|
+
logger
|
|
199
|
+
.start(ScannerLoggerEvents.analysis.tree)
|
|
200
|
+
.start(ScannerLoggerEvents.analysis.tarball)
|
|
201
|
+
.start(ScannerLoggerEvents.analysis.registry);
|
|
202
|
+
const fetchedMetadataPackages = new Set();
|
|
274
203
|
const promisesToWait = [];
|
|
275
|
-
const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode };
|
|
276
204
|
|
|
277
205
|
const tarballLocker = new Lock({ maxConcurrent: 5 });
|
|
278
|
-
|
|
279
|
-
metadataLocker.on("freeOne", () => logger.tick("registry"));
|
|
280
|
-
tarballLocker.on("freeOne", () => logger.tick("tarball"));
|
|
206
|
+
tarballLocker.on("freeOne", () => logger.tick(ScannerLoggerEvents.analysis.tarball));
|
|
281
207
|
|
|
208
|
+
const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode };
|
|
282
209
|
for await (const currentDep of getRootDependencies(manifest, rootDepsOptions)) {
|
|
283
210
|
const { name, version } = currentDep;
|
|
284
211
|
const current = currentDep.exportAsPlainObject(name === manifest.name ? 0 : void 0);
|
|
285
212
|
let proceedDependencyAnalysis = true;
|
|
286
213
|
|
|
287
|
-
if (
|
|
214
|
+
if (dependencies.has(name)) {
|
|
288
215
|
// TODO: how to handle different metadata ?
|
|
289
|
-
const dep =
|
|
216
|
+
const dep = dependencies.get(name);
|
|
290
217
|
|
|
291
|
-
const currVersion = current.versions[0];
|
|
292
|
-
if (currVersion in dep) {
|
|
218
|
+
const currVersion = Object.keys(current.versions)[0];
|
|
219
|
+
if (currVersion in dep.versions) {
|
|
293
220
|
// The dependency has already entered the analysis
|
|
294
221
|
// This happens if the package is used by multiple packages in the tree
|
|
295
222
|
proceedDependencyAnalysis = false;
|
|
296
223
|
}
|
|
297
224
|
else {
|
|
298
|
-
dep[currVersion] = current[currVersion];
|
|
299
|
-
dep.versions.push(currVersion);
|
|
225
|
+
dep.versions[currVersion] = current.versions[currVersion];
|
|
300
226
|
}
|
|
301
227
|
}
|
|
302
228
|
else {
|
|
303
|
-
|
|
229
|
+
dependencies.set(name, current);
|
|
304
230
|
}
|
|
305
231
|
|
|
306
232
|
if (proceedDependencyAnalysis) {
|
|
307
|
-
logger.tick(
|
|
233
|
+
logger.tick(ScannerLoggerEvents.analysis.tree);
|
|
234
|
+
|
|
235
|
+
// There is no need to fetch 'N' times the npm metadata for the same package.
|
|
236
|
+
if (fetchedMetadataPackages.has(name)) {
|
|
237
|
+
logger.tick(ScannerLoggerEvents.analysis.registry);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
fetchedMetadataPackages.add(name);
|
|
241
|
+
promisesToWait.push(packageMetadata(name, version, {
|
|
242
|
+
ref: current,
|
|
243
|
+
logger
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
308
246
|
|
|
309
|
-
promisesToWait.push(fetchPackageMetadata(name, version, {
|
|
310
|
-
ref: current,
|
|
311
|
-
locker: metadataLocker,
|
|
312
|
-
logger
|
|
313
|
-
}));
|
|
314
247
|
promisesToWait.push(scanDirOrArchive(name, version, {
|
|
315
|
-
ref: current[version],
|
|
248
|
+
ref: current.versions[version],
|
|
316
249
|
tmpLocation: forceRootAnalysis && name === manifest.name ? null : tmpLocation,
|
|
317
250
|
locker: tarballLocker,
|
|
318
251
|
logger
|
|
@@ -320,17 +253,17 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
|
|
|
320
253
|
}
|
|
321
254
|
}
|
|
322
255
|
|
|
323
|
-
logger.end(
|
|
256
|
+
logger.end(ScannerLoggerEvents.analysis.tree);
|
|
324
257
|
|
|
325
258
|
// Wait for all extraction to be done!
|
|
326
259
|
await Promise.allSettled(promisesToWait);
|
|
327
260
|
await timers.setImmediate();
|
|
328
261
|
|
|
329
|
-
logger.end(
|
|
262
|
+
logger.end(ScannerLoggerEvents.analysis.tarball).end(ScannerLoggerEvents.analysis.registry);
|
|
330
263
|
}
|
|
331
264
|
|
|
332
265
|
const { hydratePayloadDependencies, strategy } = await vuln.setStrategy(vulnerabilityStrategy);
|
|
333
|
-
await hydratePayloadDependencies(
|
|
266
|
+
await hydratePayloadDependencies(dependencies, {
|
|
334
267
|
useStandardFormat: true
|
|
335
268
|
});
|
|
336
269
|
|
|
@@ -338,10 +271,9 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
|
|
|
338
271
|
|
|
339
272
|
// We do this because it "seem" impossible to link all dependencies in the first walk.
|
|
340
273
|
// Because we are dealing with package only one time it may happen sometimes.
|
|
341
|
-
for (const [packageName,
|
|
342
|
-
for (const verStr of
|
|
343
|
-
|
|
344
|
-
verDescriptor.flags.push(...addMissingVersionFlags(new Set(verDescriptor.flags), descriptor));
|
|
274
|
+
for (const [packageName, dependency] of dependencies) {
|
|
275
|
+
for (const [verStr, verDescriptor] of Object.entries(dependency.versions)) {
|
|
276
|
+
verDescriptor.flags.push(...addMissingVersionFlags(new Set(verDescriptor.flags), dependency));
|
|
345
277
|
|
|
346
278
|
const fullName = `${packageName}@${verStr}`;
|
|
347
279
|
const usedDeps = exclude.get(fullName) || new Set();
|
|
@@ -358,13 +290,15 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
|
|
|
358
290
|
}
|
|
359
291
|
|
|
360
292
|
try {
|
|
361
|
-
payload.warnings = getDependenciesWarnings(
|
|
362
|
-
payload.dependencies = Object.fromEntries(
|
|
293
|
+
payload.warnings = getDependenciesWarnings(dependencies);
|
|
294
|
+
payload.dependencies = Object.fromEntries(dependencies);
|
|
363
295
|
|
|
364
296
|
return payload;
|
|
365
297
|
}
|
|
366
298
|
finally {
|
|
367
299
|
await timers.setImmediate();
|
|
368
300
|
await fs.rm(tmpLocation, { recursive: true, force: true });
|
|
301
|
+
|
|
302
|
+
logger.emit(ScannerLoggerEvents.done);
|
|
369
303
|
}
|
|
370
304
|
}
|
package/src/manifest.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// Import Third-party Dependencies
|
|
6
|
+
import { parseManifestAuthor } from "@nodesecure/utils";
|
|
7
|
+
|
|
8
|
+
// CONSTANTS
|
|
9
|
+
// PR welcome to contribute to this list!
|
|
10
|
+
const kNativeNpmPackages = new Set([
|
|
11
|
+
"node-gyp", "node-pre-gyp", "node-gyp-build", "node-addon-api"
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @see https://www.nerdycode.com/prevent-npm-executing-scripts-security/
|
|
16
|
+
*/
|
|
17
|
+
const kUnsafeNpmScripts = new Set([
|
|
18
|
+
"install",
|
|
19
|
+
"preinstall", "postinstall",
|
|
20
|
+
"preuninstall", "postuninstall"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {!string} location
|
|
25
|
+
* @returns {import("@npm/types").PackageJson}
|
|
26
|
+
*/
|
|
27
|
+
export async function read(location) {
|
|
28
|
+
const packageStr = await fs.readFile(
|
|
29
|
+
path.join(location, "package.json"),
|
|
30
|
+
"utf-8"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return JSON.parse(packageStr);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// TODO: PR @npm/types to fix dependencies typo
|
|
37
|
+
export async function readAnalyze(location) {
|
|
38
|
+
const {
|
|
39
|
+
description = "", author = {}, scripts = {},
|
|
40
|
+
dependencies = {}, devDependencies = {}, gypfile = false
|
|
41
|
+
} = await read(location);
|
|
42
|
+
|
|
43
|
+
const packageDeps = Object.keys(dependencies);
|
|
44
|
+
const packageDevDeps = Object.keys(devDependencies);
|
|
45
|
+
const hasNativePackage = [...packageDevDeps, ...packageDeps]
|
|
46
|
+
.some((pkg) => kNativeNpmPackages.has(pkg));
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
author: typeof author === "string" ? parseManifestAuthor(author) : author,
|
|
50
|
+
description,
|
|
51
|
+
hasScript: Object.keys(scripts)
|
|
52
|
+
.some((value) => kUnsafeNpmScripts.has(value.toLowerCase())),
|
|
53
|
+
packageDeps,
|
|
54
|
+
packageDevDeps,
|
|
55
|
+
hasNativeElements: hasNativePackage || gypfile
|
|
56
|
+
};
|
|
57
|
+
}
|