@nodesecure/scanner 3.0.0 → 3.1.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/index.d.ts +2 -2
- package/index.js +6 -5
- package/package.json +1 -1
- package/src/constants.js +13 -0
- package/src/depWalker.js +304 -298
- package/src/utils/analyzeDependencies.js +1 -1
- package/types/api.d.ts +5 -2
- package/types/logger.d.ts +15 -1
- package/types/scanner.d.ts +5 -2
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
|
@@ -11,6 +11,7 @@ import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
|
11
11
|
// Import Internal Dependencies
|
|
12
12
|
import { depWalker } from "./src/depWalker.js";
|
|
13
13
|
import { NPM_TOKEN } from "./src/utils/index.js";
|
|
14
|
+
import { ScannerLoggerEvents } from "./src/constants.js";
|
|
14
15
|
import Logger from "./src/class/logger.class.js";
|
|
15
16
|
import * as tarball from "./src/tarball.js";
|
|
16
17
|
|
|
@@ -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
|
}
|
|
@@ -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
package/src/constants.js
ADDED
package/src/depWalker.js
CHANGED
|
@@ -1,298 +1,304 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { readFileSync, promises as fs } from "fs";
|
|
4
|
-
import timers from "timers/promises";
|
|
5
|
-
import os from "os";
|
|
6
|
-
|
|
7
|
-
// Import Third-party Dependencies
|
|
8
|
-
import combineAsyncIterators from "combine-async-iterators";
|
|
9
|
-
import iter from "itertools";
|
|
10
|
-
import pacote from "pacote";
|
|
11
|
-
import Arborist from "@npmcli/arborist";
|
|
12
|
-
import Lock from "@slimio/lock";
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
current.
|
|
45
|
-
current.addFlag("
|
|
46
|
-
current.addFlag("
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
current.addFlag("
|
|
93
|
-
current.addFlag("
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
parent.
|
|
120
|
-
parent.addFlag("
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* @param {*}
|
|
170
|
-
* @param {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
Object.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { readFileSync, promises as fs } from "fs";
|
|
4
|
+
import timers from "timers/promises";
|
|
5
|
+
import os from "os";
|
|
6
|
+
|
|
7
|
+
// Import Third-party Dependencies
|
|
8
|
+
import combineAsyncIterators from "combine-async-iterators";
|
|
9
|
+
import iter from "itertools";
|
|
10
|
+
import pacote from "pacote";
|
|
11
|
+
import Arborist from "@npmcli/arborist";
|
|
12
|
+
import Lock from "@slimio/lock";
|
|
13
|
+
import * as vuln from "@nodesecure/vuln";
|
|
14
|
+
import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
15
|
+
import { ScannerLoggerEvents } from "./constants.js";
|
|
16
|
+
|
|
17
|
+
// Import Internal Dependencies
|
|
18
|
+
import {
|
|
19
|
+
mergeDependencies, getCleanDependencyName, getDependenciesWarnings, addMissingVersionFlags, isGitDependency,
|
|
20
|
+
NPM_TOKEN
|
|
21
|
+
} from "./utils/index.js";
|
|
22
|
+
import { scanDirOrArchive } from "./tarball.js";
|
|
23
|
+
import { packageMetadata } from "./npmRegistry.js";
|
|
24
|
+
import Dependency from "./class/dependency.class.js";
|
|
25
|
+
import Logger from "./class/logger.class.js";
|
|
26
|
+
|
|
27
|
+
const { version: packageVersion } = JSON.parse(
|
|
28
|
+
readFileSync(
|
|
29
|
+
new URL(path.join("..", "package.json"), import.meta.url)
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export async function* searchDeepDependencies(packageName, gitURL, options) {
|
|
34
|
+
const { exclude, currDepth = 0, parent, maxDepth } = options;
|
|
35
|
+
|
|
36
|
+
const { name, version, deprecated, ...pkg } = await pacote.manifest(gitURL ?? packageName, {
|
|
37
|
+
...NPM_TOKEN,
|
|
38
|
+
registry: getLocalRegistryURL(),
|
|
39
|
+
cache: `${os.homedir()}/.npm`
|
|
40
|
+
});
|
|
41
|
+
const { dependencies, customResolvers } = mergeDependencies(pkg);
|
|
42
|
+
|
|
43
|
+
const current = new Dependency(name, version, parent);
|
|
44
|
+
gitURL !== null && current.isGit(gitURL);
|
|
45
|
+
current.addFlag("isDeprecated", deprecated === true);
|
|
46
|
+
current.addFlag("hasCustomResolver", customResolvers.size > 0);
|
|
47
|
+
current.addFlag("hasDependencies", dependencies.size > 0);
|
|
48
|
+
|
|
49
|
+
if (currDepth !== maxDepth) {
|
|
50
|
+
const config = {
|
|
51
|
+
exclude, currDepth: currDepth + 1, parent: current, maxDepth
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const gitDependencies = iter.filter(customResolvers.entries(), ([, valueStr]) => isGitDependency(valueStr));
|
|
55
|
+
for (const [depName, valueStr] of gitDependencies) {
|
|
56
|
+
yield* searchDeepDependencies(depName, valueStr, config);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const depsNames = await Promise.all(iter.map(dependencies.entries(), getCleanDependencyName));
|
|
60
|
+
for (const [fullName, cleanName, isLatest] of depsNames) {
|
|
61
|
+
if (!isLatest) {
|
|
62
|
+
current.addFlag("hasOutdatedDependency");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (exclude.has(cleanName)) {
|
|
66
|
+
exclude.get(cleanName).add(current.fullName);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
exclude.set(cleanName, new Set([current.fullName]));
|
|
70
|
+
yield* searchDeepDependencies(fullName, null, config);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
yield current;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLockMode }) {
|
|
79
|
+
const { version, integrity = to.integrity } = to.package;
|
|
80
|
+
|
|
81
|
+
const updatedVersion = version === "*" || typeof version === "undefined" ? "latest" : version;
|
|
82
|
+
const current = new Dependency(currentPackageName, updatedVersion, parent);
|
|
83
|
+
|
|
84
|
+
if (fullLockMode) {
|
|
85
|
+
const { deprecated, _integrity, ...pkg } = await pacote.manifest(`${currentPackageName}@${updatedVersion}`, {
|
|
86
|
+
...NPM_TOKEN,
|
|
87
|
+
registry: getLocalRegistryURL(),
|
|
88
|
+
cache: `${os.homedir()}/.npm`
|
|
89
|
+
});
|
|
90
|
+
const { customResolvers } = mergeDependencies(pkg);
|
|
91
|
+
|
|
92
|
+
current.addFlag("hasValidIntegrity", _integrity === integrity);
|
|
93
|
+
current.addFlag("isDeprecated");
|
|
94
|
+
current.addFlag("hasCustomResolver", customResolvers.size > 0);
|
|
95
|
+
}
|
|
96
|
+
current.addFlag("hasDependencies", to.edgesOut.size > 0);
|
|
97
|
+
|
|
98
|
+
for (const [packageName, { to: toNode }] of to.edgesOut) {
|
|
99
|
+
if (toNode === null || toNode.dev) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const cleanName = `${packageName}@${toNode.package.version}`;
|
|
103
|
+
|
|
104
|
+
if (exclude.has(cleanName)) {
|
|
105
|
+
exclude.get(cleanName).add(current.fullName);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
exclude.set(cleanName, new Set([current.fullName]));
|
|
109
|
+
yield* deepReadEdges(packageName, { parent: current, to: toNode, exclude });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
yield current;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function* getRootDependencies(manifest, options) {
|
|
116
|
+
const { maxDepth = 4, exclude, usePackageLock, fullLockMode } = options;
|
|
117
|
+
|
|
118
|
+
const { dependencies, customResolvers } = mergeDependencies(manifest, void 0);
|
|
119
|
+
const parent = new Dependency(manifest.name, manifest.version);
|
|
120
|
+
parent.addFlag("hasCustomResolver", customResolvers.size > 0);
|
|
121
|
+
parent.addFlag("hasDependencies", dependencies.size > 0);
|
|
122
|
+
|
|
123
|
+
let iterators;
|
|
124
|
+
if (usePackageLock) {
|
|
125
|
+
const arb = new Arborist({
|
|
126
|
+
...NPM_TOKEN,
|
|
127
|
+
registry: getLocalRegistryURL()
|
|
128
|
+
});
|
|
129
|
+
let tree;
|
|
130
|
+
try {
|
|
131
|
+
await fs.access(path.join(process.cwd(), "node_modules"));
|
|
132
|
+
tree = await arb.loadActual();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
tree = await arb.loadVirtual();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
iterators = iter.filter(tree.edgesOut.entries(), ([, { to }]) => to !== null && !to.dev)
|
|
139
|
+
.map(([packageName, { to }]) => deepReadEdges(packageName, { to, parent, fullLockMode, exclude }));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const configRef = { exclude, maxDepth, parent };
|
|
143
|
+
iterators = [
|
|
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))
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
for await (const dep of combineAsyncIterators({}, ...iterators)) {
|
|
150
|
+
yield dep;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add root dependencies to the exclude Map (because the parent is not handled by searchDeepDependencies)
|
|
154
|
+
// if we skip this the code will fail to re-link properly dependencies in the following steps
|
|
155
|
+
const depsName = await Promise.all(iter.map(dependencies.entries(), getCleanDependencyName));
|
|
156
|
+
for (const [, fullRange, isLatest] of depsName) {
|
|
157
|
+
if (!isLatest) {
|
|
158
|
+
parent.addFlag("hasOutdatedDependency");
|
|
159
|
+
}
|
|
160
|
+
if (exclude.has(fullRange)) {
|
|
161
|
+
exclude.get(fullRange).add(parent.fullName);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
yield parent;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {*} manifest
|
|
170
|
+
* @param {*} options
|
|
171
|
+
* @param {Logger} logger
|
|
172
|
+
*/
|
|
173
|
+
export async function depWalker(manifest, options = {}, logger = new Logger()) {
|
|
174
|
+
const {
|
|
175
|
+
forceRootAnalysis = false,
|
|
176
|
+
usePackageLock = false,
|
|
177
|
+
fullLockMode = false,
|
|
178
|
+
maxDepth,
|
|
179
|
+
vulnerabilityStrategy = vuln.strategies.NONE
|
|
180
|
+
} = options;
|
|
181
|
+
|
|
182
|
+
// Create TMP directory
|
|
183
|
+
const tmpLocation = await fs.mkdtemp(path.join(os.tmpdir(), "/"));
|
|
184
|
+
|
|
185
|
+
const payload = {
|
|
186
|
+
id: tmpLocation.slice(-6),
|
|
187
|
+
rootDepencyName: manifest.name,
|
|
188
|
+
version: packageVersion,
|
|
189
|
+
vulnerabilityStrategy,
|
|
190
|
+
warnings: []
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// We are dealing with an exclude Map to avoid checking a package more than one time in searchDeepDependencies
|
|
194
|
+
const exclude = new Map();
|
|
195
|
+
const dependencies = new Map();
|
|
196
|
+
|
|
197
|
+
{
|
|
198
|
+
logger
|
|
199
|
+
.start(ScannerLoggerEvents.analysis.tree)
|
|
200
|
+
.start(ScannerLoggerEvents.analysis.tarball)
|
|
201
|
+
.start(ScannerLoggerEvents.analysis.registry);
|
|
202
|
+
const fetchedMetadataPackages = new Set();
|
|
203
|
+
const promisesToWait = [];
|
|
204
|
+
|
|
205
|
+
const tarballLocker = new Lock({ maxConcurrent: 5 });
|
|
206
|
+
tarballLocker.on("freeOne", () => logger.tick(ScannerLoggerEvents.analysis.tarball));
|
|
207
|
+
|
|
208
|
+
const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode };
|
|
209
|
+
for await (const currentDep of getRootDependencies(manifest, rootDepsOptions)) {
|
|
210
|
+
const { name, version } = currentDep;
|
|
211
|
+
const current = currentDep.exportAsPlainObject(name === manifest.name ? 0 : void 0);
|
|
212
|
+
let proceedDependencyAnalysis = true;
|
|
213
|
+
|
|
214
|
+
if (dependencies.has(name)) {
|
|
215
|
+
// TODO: how to handle different metadata ?
|
|
216
|
+
const dep = dependencies.get(name);
|
|
217
|
+
|
|
218
|
+
const currVersion = Object.keys(current.versions)[0];
|
|
219
|
+
if (currVersion in dep.versions) {
|
|
220
|
+
// The dependency has already entered the analysis
|
|
221
|
+
// This happens if the package is used by multiple packages in the tree
|
|
222
|
+
proceedDependencyAnalysis = false;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
dep.versions[currVersion] = current.versions[currVersion];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
dependencies.set(name, current);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (proceedDependencyAnalysis) {
|
|
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
|
+
}
|
|
246
|
+
|
|
247
|
+
promisesToWait.push(scanDirOrArchive(name, version, {
|
|
248
|
+
ref: current.versions[version],
|
|
249
|
+
tmpLocation: forceRootAnalysis && name === manifest.name ? null : tmpLocation,
|
|
250
|
+
locker: tarballLocker,
|
|
251
|
+
logger
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
logger.end(ScannerLoggerEvents.analysis.tree);
|
|
257
|
+
|
|
258
|
+
// Wait for all extraction to be done!
|
|
259
|
+
await Promise.allSettled(promisesToWait);
|
|
260
|
+
await timers.setImmediate();
|
|
261
|
+
|
|
262
|
+
logger.end(ScannerLoggerEvents.analysis.tarball).end(ScannerLoggerEvents.analysis.registry);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const { hydratePayloadDependencies, strategy } = await vuln.setStrategy(vulnerabilityStrategy);
|
|
266
|
+
await hydratePayloadDependencies(dependencies, {
|
|
267
|
+
useStandardFormat: true
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
payload.vulnerabilityStrategy = strategy;
|
|
271
|
+
|
|
272
|
+
// We do this because it "seem" impossible to link all dependencies in the first walk.
|
|
273
|
+
// Because we are dealing with package only one time it may happen sometimes.
|
|
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));
|
|
277
|
+
|
|
278
|
+
const fullName = `${packageName}@${verStr}`;
|
|
279
|
+
const usedDeps = exclude.get(fullName) || new Set();
|
|
280
|
+
if (usedDeps.size === 0) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const usedBy = Object.create(null);
|
|
285
|
+
for (const [name, version] of [...usedDeps].map((name) => name.split(" "))) {
|
|
286
|
+
usedBy[name] = version;
|
|
287
|
+
}
|
|
288
|
+
Object.assign(verDescriptor.usedBy, usedBy);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
payload.warnings = getDependenciesWarnings(dependencies);
|
|
294
|
+
payload.dependencies = Object.fromEntries(dependencies);
|
|
295
|
+
|
|
296
|
+
return payload;
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
await timers.setImmediate();
|
|
300
|
+
await fs.rm(tmpLocation, { recursive: true, force: true });
|
|
301
|
+
|
|
302
|
+
logger.emit(ScannerLoggerEvents.done);
|
|
303
|
+
}
|
|
304
|
+
}
|
package/types/api.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import Scanner from "./scanner";
|
|
2
|
-
import { Logger } from "./logger";
|
|
2
|
+
import { Logger, LoggerEvents } from "./logger";
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
cwd,
|
|
6
6
|
from,
|
|
7
|
-
verify
|
|
7
|
+
verify,
|
|
8
|
+
ScannerLoggerEvents
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
declare const ScannerLoggerEvents: LoggerEvents;
|
|
12
|
+
|
|
10
13
|
declare function cwd(path: string, options?: Scanner.Options, logger?: Logger): Promise<Scanner.Payload>;
|
|
11
14
|
declare function from(packageName: string, options?: Scanner.Options, logger?: Logger): Promise<Scanner.Payload>;
|
|
12
15
|
declare function verify(packageName: string): Promise<Scanner.VerifyPayload>;
|
package/types/logger.d.ts
CHANGED
|
@@ -2,7 +2,21 @@ import { EventEmitter } from "events";
|
|
|
2
2
|
|
|
3
3
|
export {
|
|
4
4
|
Logger,
|
|
5
|
-
LoggerEventData
|
|
5
|
+
LoggerEventData,
|
|
6
|
+
LoggerEvents
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface LoggerEvents {
|
|
10
|
+
readonly done: "depWalkerFinished";
|
|
11
|
+
readonly analysis: {
|
|
12
|
+
readonly tree: "walkTree";
|
|
13
|
+
readonly tarball: "tarball";
|
|
14
|
+
readonly registry: "registry";
|
|
15
|
+
};
|
|
16
|
+
readonly manifest: {
|
|
17
|
+
readonly read: "readManifest";
|
|
18
|
+
readonly fetch: "fetchManifest";
|
|
19
|
+
};
|
|
6
20
|
}
|
|
7
21
|
|
|
8
22
|
interface LoggerEventData {
|
package/types/scanner.d.ts
CHANGED
|
@@ -115,15 +115,18 @@ declare namespace Scanner {
|
|
|
115
115
|
vulnerabilities: Vuln.Strategy.StandardVulnerability[];
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
export type GlobalWarning = string[];
|
|
119
|
+
export type Dependencies = Record<string, Dependency>;
|
|
120
|
+
|
|
118
121
|
export interface Payload {
|
|
119
122
|
/** Payload unique id */
|
|
120
123
|
id: string;
|
|
121
124
|
/** Name of the analyzed package */
|
|
122
125
|
rootDependencyName: string;
|
|
123
126
|
/** Global warnings list */
|
|
124
|
-
warnings: [];
|
|
127
|
+
warnings: GlobalWarning[];
|
|
125
128
|
/** All the dependencies of the package (flattened) */
|
|
126
|
-
dependencies:
|
|
129
|
+
dependencies: Dependencies;
|
|
127
130
|
/** Version of the scanner used to generate the result */
|
|
128
131
|
version: string;
|
|
129
132
|
/** Vulnerability strategy name (npm, snyk, node) */
|