@nodesecure/scanner 2.2.0 → 3.2.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/src/depWalker.js CHANGED
@@ -1,370 +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 semver from "semver";
12
- import Arborist from "@npmcli/arborist";
13
- import Lock from "@slimio/lock";
14
- import { packument, getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
15
- import * as vuln from "@nodesecure/vuln";
16
- import { parseManifestAuthor } from "@nodesecure/utils";
17
-
18
- // Import Internal Dependencies
19
- import { mergeDependencies, constants, getCleanDependencyName, getDependenciesWarnings } from "./utils/index.js";
20
- import { scanDirOrArchive } from "./tarball.js";
21
- import Dependency from "./dependency.class.js";
22
- import Logger from "./logger.class.js";
23
-
24
- const { version: packageVersion } = JSON.parse(
25
- readFileSync(
26
- new URL(path.join("..", "package.json"), import.meta.url)
27
- )
28
- );
29
-
30
-
31
- async function* searchDeepDependencies(packageName, gitURL, options) {
32
- const isGit = typeof gitURL === "string";
33
- const { exclude, currDepth = 0, parent, maxDepth } = options;
34
-
35
- const { name, version, deprecated, ...pkg } = await pacote.manifest(isGit ? gitURL : packageName, {
36
- ...constants.NPM_TOKEN,
37
- registry: getLocalRegistryURL(),
38
- cache: `${os.homedir()}/.npm`
39
- });
40
- const { dependencies, customResolvers } = mergeDependencies(pkg);
41
-
42
- const current = new Dependency(name, version, parent);
43
- isGit && current.isGit(gitURL);
44
- current.addFlag("isDeprecated", deprecated === true);
45
- current.addFlag("hasCustomResolver", customResolvers.size > 0);
46
- current.addFlag("hasDependencies", dependencies.size > 0);
47
-
48
- if (currDepth !== maxDepth) {
49
- const config = {
50
- exclude, currDepth: currDepth + 1, parent: current, maxDepth
51
- };
52
-
53
- const gitDependencies = iter.filter(customResolvers.entries(), ([, valueStr]) => valueStr.startsWith("git+"));
54
- for (const [depName, valueStr] of gitDependencies) {
55
- yield* searchDeepDependencies(depName, valueStr.slice(4), config);
56
- }
57
-
58
- const depsNames = await Promise.all(iter.map(dependencies.entries(), getCleanDependencyName));
59
- for (const [fullName, cleanName, isLatest] of depsNames) {
60
- if (!isLatest) {
61
- current.addFlag("hasOutdatedDependency");
62
- }
63
-
64
- if (exclude.has(cleanName)) {
65
- exclude.get(cleanName).add(current.fullName);
66
- }
67
- else {
68
- exclude.set(cleanName, new Set([current.fullName]));
69
- yield* searchDeepDependencies(fullName, void 0, config);
70
- }
71
- }
72
- }
73
-
74
- yield current;
75
- }
76
-
77
- async function* deepReadEdges(currentPackageName, { to, parent, exclude, fullLockMode }) {
78
- const { version, integrity = to.integrity } = to.package;
79
-
80
- const updatedVersion = version === "*" || typeof version === "undefined" ? "latest" : version;
81
- const current = new Dependency(currentPackageName, updatedVersion, parent);
82
-
83
- if (fullLockMode) {
84
- const { deprecated, _integrity, ...pkg } = await pacote.manifest(`${currentPackageName}@${updatedVersion}`, {
85
- ...constants.NPM_TOKEN,
86
- registry: getLocalRegistryURL(),
87
- cache: `${os.homedir()}/.npm`
88
- });
89
- const { customResolvers } = mergeDependencies(pkg);
90
-
91
- current.addFlag("hasValidIntegrity", _integrity === integrity);
92
- current.addFlag("isDeprecated");
93
- current.addFlag("hasCustomResolver", customResolvers.size > 0);
94
- }
95
- current.addFlag("hasDependencies", to.edgesOut.size > 0);
96
-
97
- for (const [packageName, { to: toNode }] of to.edgesOut) {
98
- if (toNode === null || toNode.dev) {
99
- continue;
100
- }
101
- const cleanName = `${packageName}@${toNode.package.version}`;
102
-
103
- if (exclude.has(cleanName)) {
104
- exclude.get(cleanName).add(current.fullName);
105
- }
106
- else {
107
- exclude.set(cleanName, new Set([current.fullName]));
108
- yield* deepReadEdges(packageName, { parent: current, to: toNode, exclude });
109
- }
110
- }
111
- yield current;
112
- }
113
-
114
- async function fetchPackageMetadata(name, version, options) {
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) {
172
- const { maxDepth = 4, exclude, usePackageLock, fullLockMode } = options;
173
-
174
- const { dependencies, customResolvers } = mergeDependencies(manifest, void 0);
175
- const parent = new Dependency(manifest.name, manifest.version);
176
- parent.addFlag("hasCustomResolver", customResolvers.size > 0);
177
- parent.addFlag("hasDependencies", dependencies.size > 0);
178
-
179
- let iterators;
180
- if (usePackageLock) {
181
- const arb = new Arborist({
182
- ...constants.NPM_TOKEN,
183
- registry: getLocalRegistryURL()
184
- });
185
- let tree;
186
- try {
187
- await fs.access(path.join(process.cwd(), "node_modules"));
188
- tree = await arb.loadActual();
189
- }
190
- catch {
191
- tree = await arb.loadVirtual();
192
- }
193
-
194
- iterators = iter.filter(tree.edgesOut.entries(), ([, { to }]) => !to.dev)
195
- .map(([packageName, { to }]) => deepReadEdges(packageName, { to, parent, fullLockMode, exclude }));
196
- }
197
- else {
198
- const configRef = { exclude, maxDepth, parent };
199
- iterators = [
200
- ...iter.filter(customResolvers.entries(), ([, valueStr]) => valueStr.startsWith("git+"))
201
- .map(([depName, valueStr]) => searchDeepDependencies(depName, valueStr.slice(4), configRef)),
202
- ...iter.map(dependencies.entries(), ([name, ver]) => searchDeepDependencies(`${name}@${ver}`, void 0, configRef))
203
- ];
204
- }
205
- for await (const dep of combineAsyncIterators({}, ...iterators)) {
206
- yield dep;
207
- }
208
-
209
- // Add root dependencies to the exclude Map (because the parent is not handled by searchDeepDependencies)
210
- // if we skip this the code will fail to re-link properly dependencies in the following steps
211
- const depsName = await Promise.all(iter.map(dependencies.entries(), getCleanDependencyName));
212
- for (const [, fullRange, isLatest] of depsName) {
213
- if (!isLatest) {
214
- parent.addFlag("hasOutdatedDependency");
215
- }
216
- if (exclude.has(fullRange)) {
217
- exclude.get(fullRange).add(parent.fullName);
218
- }
219
- }
220
-
221
- yield parent;
222
- }
223
-
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
- /**
245
- * @param {*} manifest
246
- * @param {*} options
247
- * @param {Logger} logger
248
- */
249
- export async function depWalker(manifest, options = {}, logger = new Logger()) {
250
- const {
251
- forceRootAnalysis = false,
252
- usePackageLock = false,
253
- fullLockMode = false,
254
- maxDepth,
255
- vulnerabilityStrategy = vuln.strategies.NONE
256
- } = options;
257
-
258
- // Create TMP directory
259
- const tmpLocation = await fs.mkdtemp(path.join(os.tmpdir(), "/"));
260
-
261
- const payload = {
262
- id: tmpLocation.slice(-6),
263
- rootDepencyName: manifest.name,
264
- warnings: [],
265
- dependencies: new Map(),
266
- version: packageVersion
267
- };
268
-
269
- // We are dealing with an exclude Map to avoid checking a package more than one time in searchDeepDependencies
270
- const exclude = new Map();
271
-
272
- {
273
- logger.start("walkTree").start("tarball").start("registry");
274
- const promisesToWait = [];
275
- const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode };
276
-
277
- const tarballLocker = new Lock({ maxConcurrent: 5 });
278
- const metadataLocker = new Lock({ maxConcurrent: 10 });
279
- metadataLocker.on("freeOne", () => logger.tick("registry"));
280
- tarballLocker.on("freeOne", () => logger.tick("tarball"));
281
-
282
- for await (const currentDep of getRootDependencies(manifest, rootDepsOptions)) {
283
- const { name, version } = currentDep;
284
- const current = currentDep.exportAsPlainObject(name === manifest.name ? 0 : void 0);
285
- let proceedDependencyAnalysis = true;
286
-
287
- if (payload.dependencies.has(name)) {
288
- // TODO: how to handle different metadata ?
289
- const dep = payload.dependencies.get(name);
290
-
291
- const currVersion = current.versions[0];
292
- if (currVersion in dep) {
293
- // The dependency has already entered the analysis
294
- // This happens if the package is used by multiple packages in the tree
295
- proceedDependencyAnalysis = false;
296
- }
297
- else {
298
- dep[currVersion] = current[currVersion];
299
- dep.versions.push(currVersion);
300
- }
301
- }
302
- else {
303
- payload.dependencies.set(name, current);
304
- }
305
-
306
- if (proceedDependencyAnalysis) {
307
- logger.tick("walkTree");
308
-
309
- promisesToWait.push(fetchPackageMetadata(name, version, {
310
- ref: current,
311
- locker: metadataLocker,
312
- logger
313
- }));
314
- promisesToWait.push(scanDirOrArchive(name, version, {
315
- ref: current[version],
316
- tmpLocation: forceRootAnalysis && name === manifest.name ? null : tmpLocation,
317
- locker: tarballLocker,
318
- logger
319
- }));
320
- }
321
- }
322
-
323
- logger.end("walkTree");
324
-
325
- // Wait for all extraction to be done!
326
- await Promise.allSettled(promisesToWait);
327
- await timers.setImmediate();
328
-
329
- logger.end("tarball").end("registry");
330
- }
331
-
332
- const { hydratePayloadDependencies, strategy } = await vuln.setStrategy(vulnerabilityStrategy);
333
- await hydratePayloadDependencies(payload.dependencies, {
334
- useStandardFormat: true
335
- });
336
-
337
- payload.vulnerabilityStrategy = strategy;
338
-
339
- // We do this because it "seem" impossible to link all dependencies in the first walk.
340
- // Because we are dealing with package only one time it may happen sometimes.
341
- for (const [packageName, descriptor] of payload.dependencies) {
342
- for (const verStr of descriptor.versions) {
343
- const verDescriptor = descriptor[verStr];
344
- verDescriptor.flags.push(...addMissingVersionFlags(new Set(verDescriptor.flags), descriptor));
345
-
346
- const fullName = `${packageName}@${verStr}`;
347
- const usedDeps = exclude.get(fullName) || new Set();
348
- if (usedDeps.size === 0) {
349
- continue;
350
- }
351
-
352
- const usedBy = Object.create(null);
353
- for (const [name, version] of [...usedDeps].map((name) => name.split(" "))) {
354
- usedBy[name] = version;
355
- }
356
- Object.assign(verDescriptor.usedBy, usedBy);
357
- }
358
- }
359
-
360
- try {
361
- payload.warnings = getDependenciesWarnings(payload.dependencies);
362
- payload.dependencies = Object.fromEntries(payload.dependencies);
363
-
364
- return payload;
365
- }
366
- finally {
367
- await timers.setImmediate();
368
- await fs.rm(tmpLocation, { recursive: true, force: true });
369
- }
370
- }
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
+ rootDependencyName: manifest.name,
188
+ scannerVersion: 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
+ }