@nodesecure/scanner 5.2.1 → 6.0.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/README.md +6 -116
- package/dist/class/logger.class.d.ts +27 -0
- package/dist/class/logger.class.d.ts.map +1 -0
- package/dist/class/logger.class.js +52 -0
- package/dist/class/logger.class.js.map +1 -0
- package/dist/comparePayloads.d.ts +66 -0
- package/dist/comparePayloads.d.ts.map +1 -0
- package/dist/comparePayloads.js +147 -0
- package/dist/comparePayloads.js.map +1 -0
- package/dist/depWalker.d.ts +10 -0
- package/dist/depWalker.d.ts.map +1 -0
- package/dist/depWalker.js +205 -0
- package/dist/depWalker.js.map +1 -0
- package/dist/i18n/english.d.ts +9 -0
- package/dist/i18n/english.d.ts.map +1 -0
- package/dist/i18n/english.js +6 -0
- package/dist/i18n/english.js.map +1 -0
- package/dist/i18n/french.d.ts +9 -0
- package/dist/i18n/french.d.ts.map +1 -0
- package/dist/i18n/french.js +6 -0
- package/dist/i18n/french.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/npmRegistry.d.ts +9 -0
- package/dist/npmRegistry.d.ts.map +1 -0
- package/dist/npmRegistry.js +125 -0
- package/dist/npmRegistry.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/addMissingVersionFlags.d.ts +3 -0
- package/dist/utils/addMissingVersionFlags.d.ts.map +1 -0
- package/dist/utils/addMissingVersionFlags.js +21 -0
- package/dist/utils/addMissingVersionFlags.js.map +1 -0
- package/dist/utils/dirname.d.ts +2 -0
- package/dist/utils/dirname.d.ts.map +1 -0
- package/dist/utils/dirname.js +8 -0
- package/dist/utils/dirname.js.map +1 -0
- package/dist/utils/getLinks.d.ts +7 -0
- package/dist/utils/getLinks.d.ts.map +1 -0
- package/dist/utils/getLinks.js +32 -0
- package/dist/utils/getLinks.js.map +1 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/urlToString.d.ts +2 -0
- package/dist/utils/urlToString.d.ts.map +1 -0
- package/dist/utils/urlToString.js +6 -0
- package/dist/utils/urlToString.js.map +1 -0
- package/dist/utils/warnings.d.ts +9 -0
- package/dist/utils/warnings.d.ts.map +1 -0
- package/dist/utils/warnings.js +49 -0
- package/dist/utils/warnings.js.map +1 -0
- package/package.json +23 -42
- package/LICENSE +0 -21
- package/i18n/english.js +0 -6
- package/i18n/french.js +0 -7
- package/index.d.ts +0 -14
- package/index.js +0 -74
- package/src/class/dependency.class.js +0 -113
- package/src/class/logger.class.js +0 -54
- package/src/constants.js +0 -13
- package/src/depWalker.js +0 -388
- package/src/manifest.js +0 -94
- package/src/npmRegistry.js +0 -136
- package/src/tarball.js +0 -210
- package/src/utils/addMissingVersionFlags.js +0 -24
- package/src/utils/analyzeDependencies.js +0 -71
- package/src/utils/booleanToFlags.js +0 -12
- package/src/utils/dirname.js +0 -9
- package/src/utils/filterDependencyKind.js +0 -44
- package/src/utils/getLinks.js +0 -36
- package/src/utils/getPackageName.js +0 -21
- package/src/utils/getTarballComposition.js +0 -38
- package/src/utils/index.js +0 -18
- package/src/utils/isGitDependency.js +0 -11
- package/src/utils/isSensitiveFile.js +0 -17
- package/src/utils/mergeDependencies.js +0 -30
- package/src/utils/parseManifestAuthor.js +0 -45
- package/src/utils/semver.js +0 -62
- package/src/utils/warnings.js +0 -44
- package/types/api.d.ts +0 -15
- package/types/logger.d.ts +0 -38
- package/types/scanner.d.ts +0 -244
- package/types/tarball.d.ts +0 -63
- package/types/walker.d.ts +0 -8
package/src/manifest.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import crypto from "node:crypto";
|
|
5
|
-
|
|
6
|
-
// Import Internal Dependencies
|
|
7
|
-
import { parseAuthor } from "./utils/index.js";
|
|
8
|
-
|
|
9
|
-
// CONSTANTS
|
|
10
|
-
// PR welcome to contribute to this list!
|
|
11
|
-
const kNativeNpmPackages = new Set([
|
|
12
|
-
"node-gyp", "node-pre-gyp", "node-gyp-build", "node-addon-api"
|
|
13
|
-
]);
|
|
14
|
-
const kNodemodulesBinPrefix = "node_modules/.bin/";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @see https://www.nerdycode.com/prevent-npm-executing-scripts-security/
|
|
18
|
-
*/
|
|
19
|
-
const kUnsafeNpmScripts = new Set([
|
|
20
|
-
"install",
|
|
21
|
-
"preinstall",
|
|
22
|
-
"postinstall",
|
|
23
|
-
"preuninstall",
|
|
24
|
-
"postuninstall"
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {!string} location
|
|
29
|
-
* @returns {Promise<import("@npm/types").PackageJson>}
|
|
30
|
-
*/
|
|
31
|
-
export async function read(location) {
|
|
32
|
-
const packageStr = await fs.readFile(
|
|
33
|
-
path.join(location, "package.json"),
|
|
34
|
-
"utf-8"
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return JSON.parse(packageStr);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function readAnalyze(location) {
|
|
41
|
-
const {
|
|
42
|
-
name,
|
|
43
|
-
version,
|
|
44
|
-
description = "",
|
|
45
|
-
author = {},
|
|
46
|
-
scripts = {},
|
|
47
|
-
dependencies = {},
|
|
48
|
-
devDependencies = {},
|
|
49
|
-
gypfile = false,
|
|
50
|
-
engines = {},
|
|
51
|
-
repository = {},
|
|
52
|
-
imports = {},
|
|
53
|
-
license = ""
|
|
54
|
-
} = await read(location);
|
|
55
|
-
|
|
56
|
-
for (const [scriptName, scriptValue] of Object.entries(scripts)) {
|
|
57
|
-
if (scriptValue.startsWith(kNodemodulesBinPrefix)) {
|
|
58
|
-
scripts[scriptName] = scriptValue.replaceAll(kNodemodulesBinPrefix, "");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const integrityObj = {
|
|
63
|
-
name,
|
|
64
|
-
version,
|
|
65
|
-
dependencies,
|
|
66
|
-
license,
|
|
67
|
-
scripts
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const integrity = crypto
|
|
71
|
-
.createHash("sha256")
|
|
72
|
-
.update(JSON.stringify(integrityObj))
|
|
73
|
-
.digest("hex");
|
|
74
|
-
|
|
75
|
-
const packageDeps = Object.keys(dependencies);
|
|
76
|
-
const packageDevDeps = Object.keys(devDependencies);
|
|
77
|
-
const hasNativePackage = [...packageDevDeps, ...packageDeps]
|
|
78
|
-
.some((pkg) => kNativeNpmPackages.has(pkg));
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
author: parseAuthor(author),
|
|
82
|
-
description,
|
|
83
|
-
engines,
|
|
84
|
-
repository,
|
|
85
|
-
scripts,
|
|
86
|
-
hasScript: Object.keys(scripts)
|
|
87
|
-
.some((value) => kUnsafeNpmScripts.has(value.toLowerCase())),
|
|
88
|
-
packageDeps,
|
|
89
|
-
packageDevDeps,
|
|
90
|
-
nodejs: { imports },
|
|
91
|
-
hasNativeElements: hasNativePackage || gypfile,
|
|
92
|
-
integrity
|
|
93
|
-
};
|
|
94
|
-
}
|
package/src/npmRegistry.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import semver from "semver";
|
|
6
|
-
import { packument, packumentVersion } from "@nodesecure/npm-registry-sdk";
|
|
7
|
-
|
|
8
|
-
// Import Internal Dependencies
|
|
9
|
-
import { parseAuthor, getLinks } from "./utils/index.js";
|
|
10
|
-
|
|
11
|
-
export async function manifestMetadata(
|
|
12
|
-
name,
|
|
13
|
-
version,
|
|
14
|
-
dependency
|
|
15
|
-
) {
|
|
16
|
-
try {
|
|
17
|
-
const pkgVersion = await packumentVersion(name, version);
|
|
18
|
-
|
|
19
|
-
const integrity = getPackumentVersionIntegrity(pkgVersion);
|
|
20
|
-
Object.assign(
|
|
21
|
-
dependency.versions[version],
|
|
22
|
-
{
|
|
23
|
-
links: getLinks(pkgVersion)
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
dependency.metadata.integrity[version] = integrity;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
// Ignore
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function packageMetadata(name, version, options) {
|
|
35
|
-
const { ref, logger } = options;
|
|
36
|
-
const packageSpec = `${name}:${version}`;
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const pkg = await packument(name);
|
|
40
|
-
|
|
41
|
-
const oneYearFromToday = new Date();
|
|
42
|
-
oneYearFromToday.setFullYear(oneYearFromToday.getFullYear() - 1);
|
|
43
|
-
|
|
44
|
-
const lastVersion = pkg["dist-tags"].latest;
|
|
45
|
-
const lastUpdateAt = new Date(pkg.time[lastVersion]);
|
|
46
|
-
const metadata = {
|
|
47
|
-
author: parseAuthor(pkg.author),
|
|
48
|
-
homepage: pkg.homepage || null,
|
|
49
|
-
publishedCount: Object.values(pkg.versions).length,
|
|
50
|
-
lastVersion,
|
|
51
|
-
lastUpdateAt,
|
|
52
|
-
hasReceivedUpdateInOneYear: !(oneYearFromToday > lastUpdateAt),
|
|
53
|
-
maintainers: pkg.maintainers ?? [],
|
|
54
|
-
publishers: [],
|
|
55
|
-
integrity: {}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const isOutdated = semver.neq(version, lastVersion);
|
|
59
|
-
const flags = ref.versions[version].flags;
|
|
60
|
-
if (isOutdated) {
|
|
61
|
-
flags.push("isOutdated");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const publishers = new Set();
|
|
65
|
-
let searchForMaintainersInVersions = metadata.maintainers.length === 0;
|
|
66
|
-
for (const ver of Object.values(pkg.versions).reverse()) {
|
|
67
|
-
const versionSpec = `${ver.name}:${ver.version}`;
|
|
68
|
-
if (packageSpec === versionSpec) {
|
|
69
|
-
if (ver.deprecated && !flags.includes("isDeprecated")) {
|
|
70
|
-
flags.push("isDeprecated");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
metadata.integrity[ver.version] = getPackumentVersionIntegrity(
|
|
74
|
-
ver
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { _npmUser: npmUser, version, maintainers = [] } = ver;
|
|
79
|
-
|
|
80
|
-
const isNullOrUndefined = typeof npmUser === "undefined" || npmUser === null;
|
|
81
|
-
if (isNullOrUndefined || !("name" in npmUser) || typeof npmUser.name !== "string") {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const authorName = metadata.author?.name ?? null;
|
|
86
|
-
if (authorName === null) {
|
|
87
|
-
metadata.author = npmUser;
|
|
88
|
-
}
|
|
89
|
-
else if (npmUser.name !== metadata.author.name) {
|
|
90
|
-
metadata.hasManyPublishers = true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// TODO: add npmUser.email
|
|
94
|
-
if (!publishers.has(npmUser.name)) {
|
|
95
|
-
publishers.add(npmUser.name);
|
|
96
|
-
metadata.publishers.push({ ...npmUser, version, at: new Date(pkg.time[version]) });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (searchForMaintainersInVersions) {
|
|
100
|
-
metadata.maintainers.push(...maintainers);
|
|
101
|
-
searchForMaintainersInVersions = false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
Object.assign(ref.versions[version], { links: getLinks(pkg.versions[version]) });
|
|
106
|
-
Object.assign(ref.metadata, metadata);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// ignore
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
logger.tick("registry");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function getPackumentVersionIntegrity(packumentVersion) {
|
|
117
|
-
const { name, version, dependencies = {}, license = "", scripts = {} } = packumentVersion;
|
|
118
|
-
|
|
119
|
-
// See https://github.com/npm/cli/issues/5234
|
|
120
|
-
if ("install" in dependencies && dependencies.install === "node-gyp rebuild") {
|
|
121
|
-
delete dependencies.install;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const integrityObj = {
|
|
125
|
-
name,
|
|
126
|
-
version,
|
|
127
|
-
dependencies,
|
|
128
|
-
license,
|
|
129
|
-
scripts
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return crypto
|
|
133
|
-
.createHash("sha256")
|
|
134
|
-
.update(JSON.stringify(integrityObj))
|
|
135
|
-
.digest("hex");
|
|
136
|
-
}
|
package/src/tarball.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import timers from "timers/promises";
|
|
5
|
-
|
|
6
|
-
// Import Third-party Dependencies
|
|
7
|
-
import { runASTAnalysisOnFile } from "@nodesecure/js-x-ray";
|
|
8
|
-
import pacote from "pacote";
|
|
9
|
-
import ntlp from "@nodesecure/ntlp";
|
|
10
|
-
|
|
11
|
-
// Import Internal Dependencies
|
|
12
|
-
import {
|
|
13
|
-
getTarballComposition,
|
|
14
|
-
isSensitiveFile,
|
|
15
|
-
filterDependencyKind,
|
|
16
|
-
analyzeDependencies,
|
|
17
|
-
booleanToFlags,
|
|
18
|
-
NPM_TOKEN,
|
|
19
|
-
getSemVerWarning
|
|
20
|
-
} from "./utils/index.js";
|
|
21
|
-
import * as manifest from "./manifest.js";
|
|
22
|
-
|
|
23
|
-
// CONSTANTS
|
|
24
|
-
const kNativeCodeExtensions = new Set([".gyp", ".c", ".cpp", ".node", ".so", ".h"]);
|
|
25
|
-
const kJsExtname = new Set([".js", ".mjs", ".cjs"]);
|
|
26
|
-
|
|
27
|
-
export async function scanJavascriptFile(dest, file, packageName) {
|
|
28
|
-
const result = await runASTAnalysisOnFile(path.join(dest, file), { packageName });
|
|
29
|
-
|
|
30
|
-
const warnings = result.warnings.map((curr) => Object.assign({}, curr, { file }));
|
|
31
|
-
if (!result.ok) {
|
|
32
|
-
return {
|
|
33
|
-
file,
|
|
34
|
-
warnings,
|
|
35
|
-
isMinified: false,
|
|
36
|
-
tryDependencies: [],
|
|
37
|
-
dependencies: [],
|
|
38
|
-
filesDependencies: []
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const { packages, files } = filterDependencyKind(result.dependencies, path.dirname(file));
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
file,
|
|
45
|
-
warnings,
|
|
46
|
-
isMinified: result.isMinified,
|
|
47
|
-
tryDependencies: [...result.dependencies.getDependenciesInTryStatement()],
|
|
48
|
-
dependencies: packages,
|
|
49
|
-
filesDependencies: files
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function scanDirOrArchive(name, version, options) {
|
|
54
|
-
const { ref, location = process.cwd(), tmpLocation, locker, registry } = options;
|
|
55
|
-
|
|
56
|
-
const isNpmTarball = !(tmpLocation === null);
|
|
57
|
-
const dest = isNpmTarball ? path.join(tmpLocation, `${name}@${version}`) : location;
|
|
58
|
-
const free = await locker.acquireOne();
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
// If this is an NPM tarball then we extract it on the disk with pacote.
|
|
62
|
-
if (isNpmTarball) {
|
|
63
|
-
await pacote.extract(ref.flags.includes("isGit") ? ref.gitUrl : `${name}@${version}`, dest, {
|
|
64
|
-
...NPM_TOKEN,
|
|
65
|
-
registry,
|
|
66
|
-
cache: `${os.homedir()}/.npm`
|
|
67
|
-
});
|
|
68
|
-
await timers.setImmediate();
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Set links to an empty object because theses are generated only for NPM tarballs
|
|
72
|
-
Object.assign(ref, { links: {} });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Read the package.json at the root of the directory or archive.
|
|
76
|
-
const {
|
|
77
|
-
packageDeps,
|
|
78
|
-
packageDevDeps,
|
|
79
|
-
author,
|
|
80
|
-
description,
|
|
81
|
-
hasScript,
|
|
82
|
-
hasNativeElements,
|
|
83
|
-
nodejs,
|
|
84
|
-
engines,
|
|
85
|
-
repository,
|
|
86
|
-
scripts,
|
|
87
|
-
integrity
|
|
88
|
-
} = await manifest.readAnalyze(dest);
|
|
89
|
-
Object.assign(ref, {
|
|
90
|
-
author, description, engines, repository, scripts, integrity
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Get the composition of the (extracted) directory
|
|
94
|
-
const { ext, files, size } = await getTarballComposition(dest);
|
|
95
|
-
if (files.length === 1 && files.includes("package.json")) {
|
|
96
|
-
ref.warnings.push({
|
|
97
|
-
kind: "empty-package",
|
|
98
|
-
location: null,
|
|
99
|
-
i18n: "sast_warnings.emptyPackage",
|
|
100
|
-
severity: "Critical",
|
|
101
|
-
source: "Scanner",
|
|
102
|
-
experimental: false
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
ref.size = size;
|
|
107
|
-
ref.composition.extensions.push(...ext);
|
|
108
|
-
ref.composition.files.push(...files);
|
|
109
|
-
const hasBannedFile = files.some((path) => isSensitiveFile(path));
|
|
110
|
-
const hasNativeCode = hasNativeElements || files.some((file) => kNativeCodeExtensions.has(path.extname(file)));
|
|
111
|
-
|
|
112
|
-
// Search for minified and runtime dependencies
|
|
113
|
-
// Run a JS-X-Ray analysis on each JavaScript files of the project!
|
|
114
|
-
const fileAnalysisRaw = await Promise.allSettled(
|
|
115
|
-
files
|
|
116
|
-
.filter((name) => kJsExtname.has(path.extname(name)))
|
|
117
|
-
.map((file) => scanJavascriptFile(dest, file, name))
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const fileAnalysisResults = fileAnalysisRaw
|
|
121
|
-
.filter((promiseSettledResult) => promiseSettledResult.status === "fulfilled")
|
|
122
|
-
.map((promiseSettledResult) => promiseSettledResult.value);
|
|
123
|
-
|
|
124
|
-
ref.warnings.push(...fileAnalysisResults.flatMap((row) => row.warnings));
|
|
125
|
-
|
|
126
|
-
if (/^0(\.\d+)*$/.test(version)) {
|
|
127
|
-
ref.warnings.push(getSemVerWarning(version));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const dependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.dependencies))];
|
|
131
|
-
const filesDependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.filesDependencies))];
|
|
132
|
-
const tryDependencies = new Set(fileAnalysisResults.flatMap((row) => row.tryDependencies));
|
|
133
|
-
const minifiedFiles = fileAnalysisResults.filter((row) => row.isMinified).flatMap((row) => row.file);
|
|
134
|
-
|
|
135
|
-
const {
|
|
136
|
-
nodeDependencies, thirdPartyDependencies, subpathImportsDependencies, missingDependencies, unusedDependencies, flags
|
|
137
|
-
} = analyzeDependencies(dependencies, { packageDeps, packageDevDeps, tryDependencies, nodeImports: nodejs.imports });
|
|
138
|
-
|
|
139
|
-
ref.composition.required_thirdparty = thirdPartyDependencies;
|
|
140
|
-
ref.composition.required_subpath = Object.fromEntries(subpathImportsDependencies);
|
|
141
|
-
ref.composition.unused.push(...unusedDependencies);
|
|
142
|
-
ref.composition.missing.push(...missingDependencies);
|
|
143
|
-
ref.composition.required_files = filesDependencies;
|
|
144
|
-
ref.composition.required_nodejs = nodeDependencies;
|
|
145
|
-
ref.composition.minified = minifiedFiles;
|
|
146
|
-
|
|
147
|
-
// License
|
|
148
|
-
await timers.setImmediate();
|
|
149
|
-
const licenses = await ntlp(dest);
|
|
150
|
-
const uniqueLicenseIds = Array.isArray(licenses.uniqueLicenseIds) ? licenses.uniqueLicenseIds : [];
|
|
151
|
-
ref.license = licenses;
|
|
152
|
-
ref.license.uniqueLicenseIds = uniqueLicenseIds;
|
|
153
|
-
|
|
154
|
-
ref.flags.push(...booleanToFlags({
|
|
155
|
-
...flags,
|
|
156
|
-
hasNoLicense: uniqueLicenseIds.length === 0,
|
|
157
|
-
hasMultipleLicenses: licenses.hasMultipleLicenses,
|
|
158
|
-
hasMinifiedCode: minifiedFiles.length > 0,
|
|
159
|
-
hasWarnings: ref.warnings.length > 0 && !ref.flags.includes("hasWarnings"),
|
|
160
|
-
hasBannedFile,
|
|
161
|
-
hasNativeCode,
|
|
162
|
-
hasScript
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
// Ignore
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
free();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export async function scanPackage(dest, packageName) {
|
|
174
|
-
const { type = "script", name } = await manifest.read(dest);
|
|
175
|
-
|
|
176
|
-
await timers.setImmediate();
|
|
177
|
-
const { ext, files, size } = await getTarballComposition(dest);
|
|
178
|
-
ext.delete("");
|
|
179
|
-
|
|
180
|
-
// Search for runtime dependencies
|
|
181
|
-
const dependencies = Object.create(null);
|
|
182
|
-
const [minified, warnings] = [[], []];
|
|
183
|
-
|
|
184
|
-
const JSFiles = files.filter((name) => kJsExtname.has(path.extname(name)));
|
|
185
|
-
for (const file of JSFiles) {
|
|
186
|
-
const result = await runASTAnalysisOnFile(path.join(dest, file), {
|
|
187
|
-
packageName: packageName ?? name,
|
|
188
|
-
module: type === "module"
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
warnings.push(...result.warnings.map((curr) => Object.assign({}, curr, { file })));
|
|
192
|
-
if (!result.ok) {
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
dependencies[file] = result.dependencies.dependencies;
|
|
197
|
-
result.isMinified && minified.push(file);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
await timers.setImmediate();
|
|
201
|
-
const { uniqueLicenseIds, licenses } = await ntlp(dest);
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
files: { list: files, extensions: [...ext], minified },
|
|
205
|
-
directorySize: size,
|
|
206
|
-
uniqueLicenseIds,
|
|
207
|
-
licenses,
|
|
208
|
-
ast: { dependencies, warnings }
|
|
209
|
-
};
|
|
210
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {Set<string>} flags
|
|
3
|
-
* @param {import("../../types/scanner").Dependency} descriptor
|
|
4
|
-
*/
|
|
5
|
-
export function* addMissingVersionFlags(flags, descriptor) {
|
|
6
|
-
const { metadata, vulnerabilities = [], versions } = descriptor;
|
|
7
|
-
const semverVersions = Object.keys(versions);
|
|
8
|
-
|
|
9
|
-
if (!metadata.hasReceivedUpdateInOneYear && flags.has("hasOutdatedDependency") && !flags.has("isDead")) {
|
|
10
|
-
yield "isDead";
|
|
11
|
-
}
|
|
12
|
-
if (metadata.hasManyPublishers && !flags.has("hasManyPublishers")) {
|
|
13
|
-
yield "hasManyPublishers";
|
|
14
|
-
}
|
|
15
|
-
if (metadata.hasChangedAuthor && !flags.has("hasChangedAuthor")) {
|
|
16
|
-
yield "hasChangedAuthor";
|
|
17
|
-
}
|
|
18
|
-
if (vulnerabilities.length > 0 && !flags.has("hasVulnerabilities")) {
|
|
19
|
-
yield "hasVulnerabilities";
|
|
20
|
-
}
|
|
21
|
-
if (semverVersions.length > 1 && !flags.has("hasDuplicate")) {
|
|
22
|
-
yield "hasDuplicate";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Import Third-party Dependencies
|
|
2
|
-
import difference from "lodash.difference";
|
|
3
|
-
import builtins from "builtins";
|
|
4
|
-
|
|
5
|
-
// Import Internal Dependencies
|
|
6
|
-
import { getPackageName } from "./getPackageName.js";
|
|
7
|
-
|
|
8
|
-
// CONSTANTS
|
|
9
|
-
const kNodeModules = new Set(builtins({ experimental: true }));
|
|
10
|
-
const kExternalModules = new Set(["http", "https", "net", "http2", "dgram", "child_process"]);
|
|
11
|
-
|
|
12
|
-
export function analyzeDependencies(dependencies, deps = {}) {
|
|
13
|
-
const { packageDeps, packageDevDeps, tryDependencies, nodeImports = {} } = deps;
|
|
14
|
-
|
|
15
|
-
// See: https://nodejs.org/api/packages.html#subpath-imports
|
|
16
|
-
const subpathImportsDependencies = dependencies
|
|
17
|
-
.filter((name) => isAliasDependency(name) && name in nodeImports)
|
|
18
|
-
.map((name) => buildSubpathDependency(name, nodeImports));
|
|
19
|
-
const thirdPartyDependenciesAliased = new Set(
|
|
20
|
-
subpathImportsDependencies.flat().filter((name) => !isAliasDependency(name))
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const thirdPartyDependencies = dependencies
|
|
24
|
-
.map((name) => (packageDeps.includes(name) ? name : getPackageName(name)))
|
|
25
|
-
.filter((name) => !name.startsWith("."))
|
|
26
|
-
.filter((name) => !isNodeCoreModule(name))
|
|
27
|
-
.filter((name) => !packageDevDeps.includes(name))
|
|
28
|
-
.filter((name) => !tryDependencies.has(name));
|
|
29
|
-
|
|
30
|
-
const unusedDependencies = difference(
|
|
31
|
-
packageDeps.filter((name) => !name.startsWith("@types")),
|
|
32
|
-
[...thirdPartyDependencies, ...thirdPartyDependenciesAliased]
|
|
33
|
-
);
|
|
34
|
-
const missingDependencies = [...new Set(difference(thirdPartyDependencies, packageDeps))]
|
|
35
|
-
.filter((name) => !(name in nodeImports));
|
|
36
|
-
const nodeDependencies = dependencies.filter((name) => isNodeCoreModule(name));
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
nodeDependencies,
|
|
40
|
-
thirdPartyDependencies: [...new Set(thirdPartyDependencies)],
|
|
41
|
-
subpathImportsDependencies,
|
|
42
|
-
unusedDependencies,
|
|
43
|
-
missingDependencies,
|
|
44
|
-
|
|
45
|
-
flags: {
|
|
46
|
-
hasExternalCapacity: nodeDependencies.some((depName) => kExternalModules.has(depName)),
|
|
47
|
-
hasMissingOrUnusedDependency: unusedDependencies.length > 0 || missingDependencies.length > 0
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {!string} moduleName
|
|
54
|
-
* @returns {boolean}
|
|
55
|
-
*/
|
|
56
|
-
function isNodeCoreModule(moduleName) {
|
|
57
|
-
const cleanModuleName = moduleName.startsWith("node:") ? moduleName.slice(5) : moduleName;
|
|
58
|
-
|
|
59
|
-
// Note: We need to also check moduleName because builtins package only return true for 'node:test'.
|
|
60
|
-
return kNodeModules.has(cleanModuleName) || kNodeModules.has(moduleName);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isAliasDependency(moduleName) {
|
|
64
|
-
return moduleName.charAt(0) === "#";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function buildSubpathDependency(alias, nodeImports) {
|
|
68
|
-
const importedDependency = nodeImports[alias].node ?? nodeImports[alias].default;
|
|
69
|
-
|
|
70
|
-
return [alias, importedDependency];
|
|
71
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {Record<string, boolean>} flagsRecord
|
|
3
|
-
* @example
|
|
4
|
-
* console.log(...booleanToFlags({ hasScript: true })); // "hasScript"
|
|
5
|
-
*/
|
|
6
|
-
export function* booleanToFlags(flagsRecord) {
|
|
7
|
-
for (const [flagName, boolValue] of Object.entries(flagsRecord)) {
|
|
8
|
-
if (boolValue) {
|
|
9
|
-
yield flagName;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
package/src/utils/dirname.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
// CONSTANTS
|
|
5
|
-
const kRelativeImportPath = new Set([".", "..", "./", "../"]);
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @see https://nodejs.org/docs/latest/api/modules.html#file-modules
|
|
9
|
-
*
|
|
10
|
-
* @param {IterableIterator<string>} dependencies
|
|
11
|
-
* @param {!string} relativeFileLocation
|
|
12
|
-
*/
|
|
13
|
-
export function filterDependencyKind(dependencies, relativeFileLocation) {
|
|
14
|
-
const packages = [];
|
|
15
|
-
const files = [];
|
|
16
|
-
|
|
17
|
-
for (const moduleNameOrPath of dependencies) {
|
|
18
|
-
const firstChar = moduleNameOrPath.charAt(0);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @example
|
|
22
|
-
* require("..");
|
|
23
|
-
* require("/home/marco/foo.js");
|
|
24
|
-
*/
|
|
25
|
-
if (firstChar === "." || firstChar === "/") {
|
|
26
|
-
// Note: condition only possible for CJS
|
|
27
|
-
if (kRelativeImportPath.has(moduleNameOrPath)) {
|
|
28
|
-
files.push(path.join(moduleNameOrPath, "index.js"));
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
// Note: we are speculating that the extension is .js (but it could be .json or .node)
|
|
32
|
-
const fixedFileName = path.extname(moduleNameOrPath) === "" ?
|
|
33
|
-
`${moduleNameOrPath}.js` : moduleNameOrPath;
|
|
34
|
-
|
|
35
|
-
files.push(path.join(relativeFileLocation, fixedFileName));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
packages.push(moduleNameOrPath);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { packages, files };
|
|
44
|
-
}
|
package/src/utils/getLinks.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// CONSTANTS
|
|
2
|
-
const kVCSHosts = new Set(["github.com", "gitlab.com"]);
|
|
3
|
-
|
|
4
|
-
function getVCSRepositoryURL(link) {
|
|
5
|
-
try {
|
|
6
|
-
const url = new URL(link);
|
|
7
|
-
const { hostname, pathname } = url;
|
|
8
|
-
|
|
9
|
-
if (kVCSHosts.has(hostname) === false) {
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const [owner, repo] = pathname.split("/").filter(Boolean).map((curr) => curr.replace(".git", ""));
|
|
14
|
-
|
|
15
|
-
return `https://${hostname}/${owner}/${repo}`;
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {import("@nodesecure/npm-registry-sdk").PackumentVersion} packumentVersion
|
|
24
|
-
*/
|
|
25
|
-
export function getLinks(
|
|
26
|
-
packumentVersion
|
|
27
|
-
) {
|
|
28
|
-
const homepage = packumentVersion.homepage || null;
|
|
29
|
-
const repositoryUrl = packumentVersion.repository?.url || null;
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
npm: `https://www.npmjs.com/package/${packumentVersion.name}/v/${packumentVersion.version}`,
|
|
33
|
-
homepage,
|
|
34
|
-
repository: getVCSRepositoryURL(homepage) ?? getVCSRepositoryURL(repositoryUrl)
|
|
35
|
-
};
|
|
36
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// CONSTANTS
|
|
2
|
-
const kPackageSeparator = "/";
|
|
3
|
-
const kPackageOrgSymbol = "@";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @see https://github.com/npm/validate-npm-package-name#naming-rules
|
|
7
|
-
*
|
|
8
|
-
* @param {!string} name full package name
|
|
9
|
-
* @returns {string}
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* getPackageName("foo"); // foo
|
|
13
|
-
* getPackageName("foo/bar"); // foo
|
|
14
|
-
* getPackageName("@org/bar"); // @org/bar
|
|
15
|
-
*/
|
|
16
|
-
export function getPackageName(name) {
|
|
17
|
-
const parts = name.split(kPackageSeparator);
|
|
18
|
-
|
|
19
|
-
// Note: only scoped package are allowed to start with @
|
|
20
|
-
return name.startsWith(kPackageOrgSymbol) ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
21
|
-
}
|