@nodesecure/scanner 3.1.0 → 3.3.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 +2 -2
- package/index.js +63 -63
- package/package.json +86 -86
- package/src/depWalker.js +310 -304
- package/src/tarball.js +173 -173
- package/src/utils/warnings.js +10 -0
- package/types/api.d.ts +15 -15
- package/types/scanner.d.ts +1 -1
package/src/tarball.js
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
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, isSensitiveFile, filterDependencyKind, analyzeDependencies, booleanToFlags,
|
|
14
|
-
NPM_TOKEN
|
|
15
|
-
} from "./utils/index.js";
|
|
16
|
-
import * as manifest from "./manifest.js";
|
|
17
|
-
import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
18
|
-
|
|
19
|
-
// CONSTANTS
|
|
20
|
-
const kNativeCodeExtensions = new Set([".gyp", ".c", ".cpp", ".node", ".so", ".h"]);
|
|
21
|
-
const kJsExtname = new Set([".js", ".mjs", ".cjs"]);
|
|
22
|
-
|
|
23
|
-
export async function scanJavascriptFile(dest, file, packageName) {
|
|
24
|
-
const result = await runASTAnalysisOnFile(path.join(dest, file), { packageName });
|
|
25
|
-
|
|
26
|
-
const warnings = result.warnings.map((curr) => Object.assign({}, curr, { file }));
|
|
27
|
-
if (!result.ok) {
|
|
28
|
-
return {
|
|
29
|
-
file,
|
|
30
|
-
warnings,
|
|
31
|
-
isMinified: false,
|
|
32
|
-
tryDependencies: [],
|
|
33
|
-
dependencies: [],
|
|
34
|
-
filesDependencies: []
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const { packages, files } = filterDependencyKind(result.dependencies, path.dirname(file));
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
file,
|
|
41
|
-
warnings,
|
|
42
|
-
isMinified: result.isMinified,
|
|
43
|
-
tryDependencies: [...result.dependencies.getDependenciesInTryStatement()],
|
|
44
|
-
dependencies: packages,
|
|
45
|
-
filesDependencies: files
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function scanDirOrArchive(name, version, options) {
|
|
50
|
-
const { ref, tmpLocation, locker } = options;
|
|
51
|
-
|
|
52
|
-
const isNpmTarball = !(tmpLocation === null);
|
|
53
|
-
const dest = isNpmTarball ? path.join(tmpLocation, `${name}@${version}`) :
|
|
54
|
-
const free = await locker.acquireOne();
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
// If this is an NPM tarball then we extract it on the disk with pacote.
|
|
58
|
-
if (isNpmTarball) {
|
|
59
|
-
await pacote.extract(ref.flags.includes("isGit") ? ref.gitUrl : `${name}@${version}`, dest, {
|
|
60
|
-
...NPM_TOKEN,
|
|
61
|
-
registry: getLocalRegistryURL(),
|
|
62
|
-
cache: `${os.homedir()}/.npm`
|
|
63
|
-
});
|
|
64
|
-
await timers.setImmediate();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Read the package.json at the root of the directory or archive.
|
|
68
|
-
const { packageDeps, packageDevDeps, author, description, hasScript, hasNativeElements } = await manifest.readAnalyze(dest);
|
|
69
|
-
ref.author = author;
|
|
70
|
-
ref.description = description;
|
|
71
|
-
|
|
72
|
-
// Get the composition of the (extracted) directory
|
|
73
|
-
const { ext, files, size } = await getTarballComposition(dest);
|
|
74
|
-
ref.size = size;
|
|
75
|
-
ref.composition.extensions.push(...ext);
|
|
76
|
-
ref.composition.files.push(...files);
|
|
77
|
-
const hasBannedFile = files.some((path) => isSensitiveFile(path));
|
|
78
|
-
const hasNativeCode = hasNativeElements || files.some((file) => kNativeCodeExtensions.has(path.extname(file)));
|
|
79
|
-
|
|
80
|
-
// Search for minified and runtime dependencies
|
|
81
|
-
// Run a JS-X-Ray analysis on each JavaScript files of the project!
|
|
82
|
-
const fileAnalysisRaw = await Promise.allSettled(
|
|
83
|
-
files
|
|
84
|
-
.filter((name) => kJsExtname.has(path.extname(name)))
|
|
85
|
-
.map((file) => scanJavascriptFile(dest, file, name))
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const fileAnalysisResults = fileAnalysisRaw
|
|
89
|
-
.filter((promiseSettledResult) => promiseSettledResult.status === "fulfilled")
|
|
90
|
-
.map((promiseSettledResult) => promiseSettledResult.value);
|
|
91
|
-
|
|
92
|
-
ref.warnings.push(...fileAnalysisResults.flatMap((row) => row.warnings));
|
|
93
|
-
|
|
94
|
-
const dependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.dependencies))];
|
|
95
|
-
const filesDependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.filesDependencies))];
|
|
96
|
-
const tryDependencies = new Set(fileAnalysisResults.flatMap((row) => row.tryDependencies));
|
|
97
|
-
const minifiedFiles = fileAnalysisResults.filter((row) => row.isMinified).flatMap((row) => row.file);
|
|
98
|
-
|
|
99
|
-
const {
|
|
100
|
-
nodeDependencies, thirdPartyDependencies, missingDependencies, unusedDependencies, flags
|
|
101
|
-
} = analyzeDependencies(dependencies, { packageDeps, packageDevDeps, tryDependencies });
|
|
102
|
-
|
|
103
|
-
ref.composition.required_thirdparty = thirdPartyDependencies;
|
|
104
|
-
ref.composition.unused.push(...unusedDependencies);
|
|
105
|
-
ref.composition.missing.push(...missingDependencies);
|
|
106
|
-
ref.composition.required_files = filesDependencies;
|
|
107
|
-
ref.composition.required_nodejs = nodeDependencies;
|
|
108
|
-
ref.composition.minified = minifiedFiles;
|
|
109
|
-
|
|
110
|
-
// License
|
|
111
|
-
await timers.setImmediate();
|
|
112
|
-
const licenses = await ntlp(dest);
|
|
113
|
-
const uniqueLicenseIds = Array.isArray(licenses.uniqueLicenseIds) ? licenses.uniqueLicenseIds : [];
|
|
114
|
-
ref.license = licenses;
|
|
115
|
-
ref.license.uniqueLicenseIds = uniqueLicenseIds;
|
|
116
|
-
|
|
117
|
-
ref.flags.push(...booleanToFlags({
|
|
118
|
-
...flags,
|
|
119
|
-
hasNoLicense: uniqueLicenseIds.length === 0,
|
|
120
|
-
hasMultipleLicenses: licenses.hasMultipleLicenses,
|
|
121
|
-
hasMinifiedCode: minifiedFiles.length > 0,
|
|
122
|
-
hasWarnings: ref.warnings.length > 0 && !ref.flags.includes("hasWarnings"),
|
|
123
|
-
hasBannedFile,
|
|
124
|
-
hasNativeCode,
|
|
125
|
-
hasScript
|
|
126
|
-
}));
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
// Ignore
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
free();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function scanPackage(dest, packageName) {
|
|
137
|
-
const { type = "script", name } = await manifest.read(dest);
|
|
138
|
-
|
|
139
|
-
await timers.setImmediate();
|
|
140
|
-
const { ext, files, size } = await getTarballComposition(dest);
|
|
141
|
-
ext.delete("");
|
|
142
|
-
|
|
143
|
-
// Search for runtime dependencies
|
|
144
|
-
const dependencies = Object.create(null);
|
|
145
|
-
const [minified, warnings] = [[], []];
|
|
146
|
-
|
|
147
|
-
const JSFiles = files.filter((name) => kJsExtname.has(path.extname(name)));
|
|
148
|
-
for (const file of JSFiles) {
|
|
149
|
-
const result = await runASTAnalysisOnFile(path.join(dest, file), {
|
|
150
|
-
packageName: packageName ?? name,
|
|
151
|
-
module: type === "module"
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
warnings.push(...result.warnings.map((curr) => Object.assign({}, curr, { file })));
|
|
155
|
-
if (!result.ok) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
dependencies[file] = result.dependencies.dependencies;
|
|
160
|
-
result.isMinified && minified.push(file);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
await timers.setImmediate();
|
|
164
|
-
const { uniqueLicenseIds, licenses } = await ntlp(dest);
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
files: { list: files, extensions: [...ext], minified },
|
|
168
|
-
directorySize: size,
|
|
169
|
-
uniqueLicenseIds,
|
|
170
|
-
licenses,
|
|
171
|
-
ast: { dependencies, warnings }
|
|
172
|
-
};
|
|
173
|
-
}
|
|
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, isSensitiveFile, filterDependencyKind, analyzeDependencies, booleanToFlags,
|
|
14
|
+
NPM_TOKEN
|
|
15
|
+
} from "./utils/index.js";
|
|
16
|
+
import * as manifest from "./manifest.js";
|
|
17
|
+
import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
|
|
18
|
+
|
|
19
|
+
// CONSTANTS
|
|
20
|
+
const kNativeCodeExtensions = new Set([".gyp", ".c", ".cpp", ".node", ".so", ".h"]);
|
|
21
|
+
const kJsExtname = new Set([".js", ".mjs", ".cjs"]);
|
|
22
|
+
|
|
23
|
+
export async function scanJavascriptFile(dest, file, packageName) {
|
|
24
|
+
const result = await runASTAnalysisOnFile(path.join(dest, file), { packageName });
|
|
25
|
+
|
|
26
|
+
const warnings = result.warnings.map((curr) => Object.assign({}, curr, { file }));
|
|
27
|
+
if (!result.ok) {
|
|
28
|
+
return {
|
|
29
|
+
file,
|
|
30
|
+
warnings,
|
|
31
|
+
isMinified: false,
|
|
32
|
+
tryDependencies: [],
|
|
33
|
+
dependencies: [],
|
|
34
|
+
filesDependencies: []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const { packages, files } = filterDependencyKind(result.dependencies, path.dirname(file));
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
file,
|
|
41
|
+
warnings,
|
|
42
|
+
isMinified: result.isMinified,
|
|
43
|
+
tryDependencies: [...result.dependencies.getDependenciesInTryStatement()],
|
|
44
|
+
dependencies: packages,
|
|
45
|
+
filesDependencies: files
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function scanDirOrArchive(name, version, options) {
|
|
50
|
+
const { ref, location = process.cwd(), tmpLocation, locker } = options;
|
|
51
|
+
|
|
52
|
+
const isNpmTarball = !(tmpLocation === null);
|
|
53
|
+
const dest = isNpmTarball ? path.join(tmpLocation, `${name}@${version}`) : location;
|
|
54
|
+
const free = await locker.acquireOne();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// If this is an NPM tarball then we extract it on the disk with pacote.
|
|
58
|
+
if (isNpmTarball) {
|
|
59
|
+
await pacote.extract(ref.flags.includes("isGit") ? ref.gitUrl : `${name}@${version}`, dest, {
|
|
60
|
+
...NPM_TOKEN,
|
|
61
|
+
registry: getLocalRegistryURL(),
|
|
62
|
+
cache: `${os.homedir()}/.npm`
|
|
63
|
+
});
|
|
64
|
+
await timers.setImmediate();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Read the package.json at the root of the directory or archive.
|
|
68
|
+
const { packageDeps, packageDevDeps, author, description, hasScript, hasNativeElements } = await manifest.readAnalyze(dest);
|
|
69
|
+
ref.author = author;
|
|
70
|
+
ref.description = description;
|
|
71
|
+
|
|
72
|
+
// Get the composition of the (extracted) directory
|
|
73
|
+
const { ext, files, size } = await getTarballComposition(dest);
|
|
74
|
+
ref.size = size;
|
|
75
|
+
ref.composition.extensions.push(...ext);
|
|
76
|
+
ref.composition.files.push(...files);
|
|
77
|
+
const hasBannedFile = files.some((path) => isSensitiveFile(path));
|
|
78
|
+
const hasNativeCode = hasNativeElements || files.some((file) => kNativeCodeExtensions.has(path.extname(file)));
|
|
79
|
+
|
|
80
|
+
// Search for minified and runtime dependencies
|
|
81
|
+
// Run a JS-X-Ray analysis on each JavaScript files of the project!
|
|
82
|
+
const fileAnalysisRaw = await Promise.allSettled(
|
|
83
|
+
files
|
|
84
|
+
.filter((name) => kJsExtname.has(path.extname(name)))
|
|
85
|
+
.map((file) => scanJavascriptFile(dest, file, name))
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const fileAnalysisResults = fileAnalysisRaw
|
|
89
|
+
.filter((promiseSettledResult) => promiseSettledResult.status === "fulfilled")
|
|
90
|
+
.map((promiseSettledResult) => promiseSettledResult.value);
|
|
91
|
+
|
|
92
|
+
ref.warnings.push(...fileAnalysisResults.flatMap((row) => row.warnings));
|
|
93
|
+
|
|
94
|
+
const dependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.dependencies))];
|
|
95
|
+
const filesDependencies = [...new Set(fileAnalysisResults.flatMap((row) => row.filesDependencies))];
|
|
96
|
+
const tryDependencies = new Set(fileAnalysisResults.flatMap((row) => row.tryDependencies));
|
|
97
|
+
const minifiedFiles = fileAnalysisResults.filter((row) => row.isMinified).flatMap((row) => row.file);
|
|
98
|
+
|
|
99
|
+
const {
|
|
100
|
+
nodeDependencies, thirdPartyDependencies, missingDependencies, unusedDependencies, flags
|
|
101
|
+
} = analyzeDependencies(dependencies, { packageDeps, packageDevDeps, tryDependencies });
|
|
102
|
+
|
|
103
|
+
ref.composition.required_thirdparty = thirdPartyDependencies;
|
|
104
|
+
ref.composition.unused.push(...unusedDependencies);
|
|
105
|
+
ref.composition.missing.push(...missingDependencies);
|
|
106
|
+
ref.composition.required_files = filesDependencies;
|
|
107
|
+
ref.composition.required_nodejs = nodeDependencies;
|
|
108
|
+
ref.composition.minified = minifiedFiles;
|
|
109
|
+
|
|
110
|
+
// License
|
|
111
|
+
await timers.setImmediate();
|
|
112
|
+
const licenses = await ntlp(dest);
|
|
113
|
+
const uniqueLicenseIds = Array.isArray(licenses.uniqueLicenseIds) ? licenses.uniqueLicenseIds : [];
|
|
114
|
+
ref.license = licenses;
|
|
115
|
+
ref.license.uniqueLicenseIds = uniqueLicenseIds;
|
|
116
|
+
|
|
117
|
+
ref.flags.push(...booleanToFlags({
|
|
118
|
+
...flags,
|
|
119
|
+
hasNoLicense: uniqueLicenseIds.length === 0,
|
|
120
|
+
hasMultipleLicenses: licenses.hasMultipleLicenses,
|
|
121
|
+
hasMinifiedCode: minifiedFiles.length > 0,
|
|
122
|
+
hasWarnings: ref.warnings.length > 0 && !ref.flags.includes("hasWarnings"),
|
|
123
|
+
hasBannedFile,
|
|
124
|
+
hasNativeCode,
|
|
125
|
+
hasScript
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Ignore
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
free();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function scanPackage(dest, packageName) {
|
|
137
|
+
const { type = "script", name } = await manifest.read(dest);
|
|
138
|
+
|
|
139
|
+
await timers.setImmediate();
|
|
140
|
+
const { ext, files, size } = await getTarballComposition(dest);
|
|
141
|
+
ext.delete("");
|
|
142
|
+
|
|
143
|
+
// Search for runtime dependencies
|
|
144
|
+
const dependencies = Object.create(null);
|
|
145
|
+
const [minified, warnings] = [[], []];
|
|
146
|
+
|
|
147
|
+
const JSFiles = files.filter((name) => kJsExtname.has(path.extname(name)));
|
|
148
|
+
for (const file of JSFiles) {
|
|
149
|
+
const result = await runASTAnalysisOnFile(path.join(dest, file), {
|
|
150
|
+
packageName: packageName ?? name,
|
|
151
|
+
module: type === "module"
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
warnings.push(...result.warnings.map((curr) => Object.assign({}, curr, { file })));
|
|
155
|
+
if (!result.ok) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dependencies[file] = result.dependencies.dependencies;
|
|
160
|
+
result.isMinified && minified.push(file);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await timers.setImmediate();
|
|
164
|
+
const { uniqueLicenseIds, licenses } = await ntlp(dest);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
files: { list: files, extensions: [...ext], minified },
|
|
168
|
+
directorySize: size,
|
|
169
|
+
uniqueLicenseIds,
|
|
170
|
+
licenses,
|
|
171
|
+
ast: { dependencies, warnings }
|
|
172
|
+
};
|
|
173
|
+
}
|
package/src/utils/warnings.js
CHANGED
|
@@ -8,6 +8,7 @@ const kWarningsMessages = Object.freeze({
|
|
|
8
8
|
iohook: getToken("warnings.keylogging")
|
|
9
9
|
});
|
|
10
10
|
const kPackages = new Set(Object.keys(kWarningsMessages));
|
|
11
|
+
const kAuthors = new Set(["marak", "marak.squires@gmail.com"]);
|
|
11
12
|
|
|
12
13
|
function getWarning(depName) {
|
|
13
14
|
return `${kDetectedDep(depName)} ${kWarningsMessages[depName]}`;
|
|
@@ -21,6 +22,15 @@ export function getDependenciesWarnings(dependencies) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// TODO: optimize with @nodesecure/author later
|
|
26
|
+
for (const [packageName, dependency] of dependencies) {
|
|
27
|
+
for (const { name, email } of dependency.metadata.maintainers) {
|
|
28
|
+
if (kAuthors.has(name) || kAuthors.has(email)) {
|
|
29
|
+
warnings.push(`'Marak Squires' package '${packageName}' has been detected in the dependency tree`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
return warnings;
|
|
25
35
|
}
|
|
26
36
|
|
package/types/api.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import Scanner from "./scanner";
|
|
2
|
-
import { Logger, LoggerEvents } from "./logger";
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
cwd,
|
|
6
|
-
from,
|
|
7
|
-
verify,
|
|
8
|
-
ScannerLoggerEvents
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
declare const ScannerLoggerEvents: LoggerEvents;
|
|
12
|
-
|
|
13
|
-
declare function cwd(
|
|
14
|
-
declare function from(packageName: string, options?: Scanner.Options, logger?: Logger): Promise<Scanner.Payload>;
|
|
15
|
-
declare function verify(packageName
|
|
1
|
+
import Scanner from "./scanner";
|
|
2
|
+
import { Logger, LoggerEvents } from "./logger";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
cwd,
|
|
6
|
+
from,
|
|
7
|
+
verify,
|
|
8
|
+
ScannerLoggerEvents
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare const ScannerLoggerEvents: LoggerEvents;
|
|
12
|
+
|
|
13
|
+
declare function cwd(location: string, options?: Scanner.Options, logger?: Logger): Promise<Scanner.Payload>;
|
|
14
|
+
declare function from(packageName: string, options?: Scanner.Options, logger?: Logger): Promise<Scanner.Payload>;
|
|
15
|
+
declare function verify(packageName?: string | null): Promise<Scanner.VerifyPayload>;
|
package/types/scanner.d.ts
CHANGED
|
@@ -128,7 +128,7 @@ declare namespace Scanner {
|
|
|
128
128
|
/** All the dependencies of the package (flattened) */
|
|
129
129
|
dependencies: Dependencies;
|
|
130
130
|
/** Version of the scanner used to generate the result */
|
|
131
|
-
|
|
131
|
+
scannerVersion: string;
|
|
132
132
|
/** Vulnerability strategy name (npm, snyk, node) */
|
|
133
133
|
vulnerabilityStrategy: Vuln.Strategy.Kind;
|
|
134
134
|
}
|