@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/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}`) : process.cwd();
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
+ }
@@ -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(path: 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): Promise<Scanner.VerifyPayload>;
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>;
@@ -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
- version: string;
131
+ scannerVersion: string;
132
132
  /** Vulnerability strategy name (npm, snyk, node) */
133
133
  vulnerabilityStrategy: Vuln.Strategy.Kind;
134
134
  }