@nodesecure/scanner 3.2.1 → 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 CHANGED
@@ -49,9 +49,9 @@ await Promise.allSettled(promises);
49
49
  See `types/api.d.ts` for a complete TypeScript definition.
50
50
 
51
51
  ```ts
52
- function cwd(path: string, options?: Scanner.Options): Promise<Scanner.Payload>;
52
+ function cwd(location: string, options?: Scanner.Options): Promise<Scanner.Payload>;
53
53
  function from(packageName: string, options?: Scanner.Options): Promise<Scanner.Payload>;
54
- function verify(packageName: string): Promise<Scanner.VerifyPayload>;
54
+ function verify(packageName?: string | null): Promise<Scanner.VerifyPayload>;
55
55
  ```
56
56
 
57
57
  `Options` is described with the following TypeScript interface:
package/index.js CHANGED
@@ -1,63 +1,63 @@
1
- // Import Node.js Dependencies
2
- import path from "path";
3
- import fs from "fs/promises";
4
- import timers from "timers/promises";
5
- import os from "os";
6
-
7
- // Import Third-party Dependencies
8
- import pacote from "pacote";
9
- import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
10
-
11
- // Import Internal Dependencies
12
- import { depWalker } from "./src/depWalker.js";
13
- import { NPM_TOKEN } from "./src/utils/index.js";
14
- import { ScannerLoggerEvents } from "./src/constants.js";
15
- import Logger from "./src/class/logger.class.js";
16
- import * as tarball from "./src/tarball.js";
17
-
18
- // CONSTANTS
19
- const kDefaultCwdOptions = { forceRootAnalysis: true, usePackageLock: true };
20
-
21
- export async function cwd(cwd = process.cwd(), options = {}, logger = new Logger()) {
22
- const finalizedOptions = Object.assign({}, kDefaultCwdOptions, options);
23
-
24
- logger.start(ScannerLoggerEvents.manifest.read);
25
- const packagePath = path.join(cwd, "package.json");
26
- const str = await fs.readFile(packagePath, "utf-8");
27
- logger.end(ScannerLoggerEvents.manifest.read);
28
-
29
- return depWalker(JSON.parse(str), finalizedOptions, logger);
30
- }
31
-
32
- export async function from(packageName, options, logger = new Logger()) {
33
- logger.start(ScannerLoggerEvents.manifest.fetch);
34
- const manifest = await pacote.manifest(packageName, {
35
- ...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
36
- });
37
- logger.end(ScannerLoggerEvents.manifest.fetch);
38
-
39
- return depWalker(manifest, options, logger);
40
- }
41
-
42
- export async function verify(packageName = null) {
43
- if (typeof packageName === "undefined" || packageName === null) {
44
- return await tarball.scanPackage(process.cwd());
45
- }
46
-
47
- const tmpLocation = await fs.mkdtemp(path.join(os.tmpdir(), "/"));
48
- const dest = path.join(tmpLocation, packageName);
49
-
50
- try {
51
- await pacote.extract(packageName, dest, {
52
- ...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
53
- });
54
-
55
- return await tarball.scanPackage(dest, packageName);
56
- }
57
- finally {
58
- await timers.setImmediate();
59
- await fs.rm(tmpLocation, { recursive: true, force: true });
60
- }
61
- }
62
-
63
- export { depWalker, tarball, Logger, ScannerLoggerEvents };
1
+ // Import Node.js Dependencies
2
+ import path from "path";
3
+ import fs from "fs/promises";
4
+ import timers from "timers/promises";
5
+ import os from "os";
6
+
7
+ // Import Third-party Dependencies
8
+ import pacote from "pacote";
9
+ import { getLocalRegistryURL } from "@nodesecure/npm-registry-sdk";
10
+
11
+ // Import Internal Dependencies
12
+ import { depWalker } from "./src/depWalker.js";
13
+ import { NPM_TOKEN } from "./src/utils/index.js";
14
+ import { ScannerLoggerEvents } from "./src/constants.js";
15
+ import Logger from "./src/class/logger.class.js";
16
+ import * as tarball from "./src/tarball.js";
17
+
18
+ // CONSTANTS
19
+ const kDefaultCwdOptions = { forceRootAnalysis: true, usePackageLock: true };
20
+
21
+ export async function cwd(location = process.cwd(), options = {}, logger = new Logger()) {
22
+ const finalizedOptions = Object.assign({ location }, kDefaultCwdOptions, options);
23
+
24
+ logger.start(ScannerLoggerEvents.manifest.read);
25
+ const packagePath = path.join(location, "package.json");
26
+ const str = await fs.readFile(packagePath, "utf-8");
27
+ logger.end(ScannerLoggerEvents.manifest.read);
28
+
29
+ return depWalker(JSON.parse(str), finalizedOptions, logger);
30
+ }
31
+
32
+ export async function from(packageName, options, logger = new Logger()) {
33
+ logger.start(ScannerLoggerEvents.manifest.fetch);
34
+ const manifest = await pacote.manifest(packageName, {
35
+ ...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
36
+ });
37
+ logger.end(ScannerLoggerEvents.manifest.fetch);
38
+
39
+ return depWalker(manifest, options, logger);
40
+ }
41
+
42
+ export async function verify(packageName = null) {
43
+ if (typeof packageName === "undefined" || packageName === null) {
44
+ return await tarball.scanPackage(process.cwd());
45
+ }
46
+
47
+ const tmpLocation = await fs.mkdtemp(path.join(os.tmpdir(), "/"));
48
+ const dest = path.join(tmpLocation, packageName);
49
+
50
+ try {
51
+ await pacote.extract(packageName, dest, {
52
+ ...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
53
+ });
54
+
55
+ return await tarball.scanPackage(dest, packageName);
56
+ }
57
+ finally {
58
+ await timers.setImmediate();
59
+ await fs.rm(tmpLocation, { recursive: true, force: true });
60
+ }
61
+ }
62
+
63
+ export { depWalker, tarball, Logger, ScannerLoggerEvents };
package/package.json CHANGED
@@ -1,86 +1,86 @@
1
- {
2
- "name": "@nodesecure/scanner",
3
- "version": "3.2.1",
4
- "description": "A package API to run a static analysis of your module's dependencies.",
5
- "exports": "./index.js",
6
- "engines": {
7
- "node": ">=16"
8
- },
9
- "scripts": {
10
- "lint": "eslint src test",
11
- "prepublishOnly": "pkg-ok",
12
- "test": "npm run lint && npm run test-only",
13
- "test-only": "cross-env esm-tape-runner 'test/**/*.spec.js' | tap-monkey",
14
- "coverage": "c8 -r html npm run test-only"
15
- },
16
- "files": [
17
- "src",
18
- "types",
19
- "index.js",
20
- "index.d.ts"
21
- ],
22
- "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/NodeSecure/scanner.git"
25
- },
26
- "keywords": [
27
- "node",
28
- "nodejs",
29
- "security",
30
- "cli",
31
- "sast",
32
- "scanner",
33
- "static",
34
- "code",
35
- "analysis",
36
- "node_modules",
37
- "tree",
38
- "npm",
39
- "registry",
40
- "graph",
41
- "visualization",
42
- "dependencies"
43
- ],
44
- "author": "NodeSecure",
45
- "license": "MIT",
46
- "bugs": {
47
- "url": "https://github.com/NodeSecure/scanner/issues"
48
- },
49
- "homepage": "https://github.com/NodeSecure/scanner#readme",
50
- "devDependencies": {
51
- "@nodesecure/eslint-config": "^1.3.0",
52
- "@slimio/is": "^1.5.1",
53
- "@small-tech/esm-tape-runner": "^1.0.3",
54
- "@small-tech/tap-monkey": "^1.3.0",
55
- "@types/node": "^16.11.10",
56
- "c8": "^7.10.0",
57
- "cross-env": "^7.0.3",
58
- "dotenv": "^10.0.0",
59
- "eslint": "^8.3.0",
60
- "get-folder-size": "^3.1.0",
61
- "pkg-ok": "^2.3.1",
62
- "sinon": "^12.0.1",
63
- "snap-shot-core": "^10.2.4",
64
- "tape": "^5.3.2"
65
- },
66
- "dependencies": {
67
- "@nodesecure/flags": "^2.2.0",
68
- "@nodesecure/fs-walk": "^1.0.0",
69
- "@nodesecure/i18n": "^1.2.1",
70
- "@nodesecure/js-x-ray": "^4.2.0",
71
- "@nodesecure/npm-registry-sdk": "^1.3.0",
72
- "@nodesecure/ntlp": "^2.1.0",
73
- "@nodesecure/utils": "^1.0.0",
74
- "@nodesecure/vuln": "^1.4.1",
75
- "@npm/types": "^1.0.1",
76
- "@npmcli/arborist": "^4.1.0",
77
- "@slimio/lock": "^1.0.0",
78
- "builtins": "^4.0.0",
79
- "combine-async-iterators": "^2.0.1",
80
- "itertools": "^1.7.1",
81
- "lodash.difference": "^4.5.0",
82
- "pacote": "^12.0.2",
83
- "semver": "^7.3.4"
84
- },
85
- "type": "module"
86
- }
1
+ {
2
+ "name": "@nodesecure/scanner",
3
+ "version": "3.3.0",
4
+ "description": "A package API to run a static analysis of your module's dependencies.",
5
+ "exports": "./index.js",
6
+ "engines": {
7
+ "node": ">=16"
8
+ },
9
+ "scripts": {
10
+ "lint": "eslint src test",
11
+ "prepublishOnly": "pkg-ok",
12
+ "test": "npm run lint && npm run test-only",
13
+ "test-only": "cross-env esm-tape-runner 'test/**/*.spec.js' | tap-monkey",
14
+ "coverage": "c8 -r html npm run test-only"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "types",
19
+ "index.js",
20
+ "index.d.ts"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/NodeSecure/scanner.git"
25
+ },
26
+ "keywords": [
27
+ "node",
28
+ "nodejs",
29
+ "security",
30
+ "cli",
31
+ "sast",
32
+ "scanner",
33
+ "static",
34
+ "code",
35
+ "analysis",
36
+ "node_modules",
37
+ "tree",
38
+ "npm",
39
+ "registry",
40
+ "graph",
41
+ "visualization",
42
+ "dependencies"
43
+ ],
44
+ "author": "NodeSecure",
45
+ "license": "MIT",
46
+ "bugs": {
47
+ "url": "https://github.com/NodeSecure/scanner/issues"
48
+ },
49
+ "homepage": "https://github.com/NodeSecure/scanner#readme",
50
+ "devDependencies": {
51
+ "@nodesecure/eslint-config": "^1.3.1",
52
+ "@slimio/is": "^1.5.1",
53
+ "@small-tech/esm-tape-runner": "^1.0.3",
54
+ "@small-tech/tap-monkey": "^1.3.0",
55
+ "@types/node": "^17.0.13",
56
+ "c8": "^7.11.0",
57
+ "cross-env": "^7.0.3",
58
+ "dotenv": "^14.3.2",
59
+ "eslint": "^8.7.0",
60
+ "get-folder-size": "^3.1.0",
61
+ "pkg-ok": "^2.3.1",
62
+ "sinon": "^12.0.1",
63
+ "snap-shot-core": "^10.2.4",
64
+ "tape": "^5.5.0"
65
+ },
66
+ "dependencies": {
67
+ "@nodesecure/flags": "^2.2.0",
68
+ "@nodesecure/fs-walk": "^1.0.0",
69
+ "@nodesecure/i18n": "^1.2.1",
70
+ "@nodesecure/js-x-ray": "^4.2.0",
71
+ "@nodesecure/npm-registry-sdk": "^1.3.0",
72
+ "@nodesecure/ntlp": "^2.1.0",
73
+ "@nodesecure/utils": "^1.0.0",
74
+ "@nodesecure/vuln": "^1.5.0",
75
+ "@npm/types": "^1.0.1",
76
+ "@npmcli/arborist": "^4.3.0",
77
+ "@slimio/lock": "^1.0.0",
78
+ "builtins": "^4.0.0",
79
+ "combine-async-iterators": "^2.0.1",
80
+ "itertools": "^1.7.1",
81
+ "lodash.difference": "^4.5.0",
82
+ "pacote": "^12.0.3",
83
+ "semver": "^7.3.4"
84
+ },
85
+ "type": "module"
86
+ }
package/src/depWalker.js CHANGED
@@ -113,7 +113,7 @@ export async function* deepReadEdges(currentPackageName, { to, parent, exclude,
113
113
  }
114
114
 
115
115
  export async function* getRootDependencies(manifest, options) {
116
- const { maxDepth = 4, exclude, usePackageLock, fullLockMode } = options;
116
+ const { maxDepth = 4, exclude, usePackageLock, fullLockMode, location } = options;
117
117
 
118
118
  const { dependencies, customResolvers } = mergeDependencies(manifest, void 0);
119
119
  const parent = new Dependency(manifest.name, manifest.version);
@@ -124,19 +124,23 @@ export async function* getRootDependencies(manifest, options) {
124
124
  if (usePackageLock) {
125
125
  const arb = new Arborist({
126
126
  ...NPM_TOKEN,
127
+ path: location,
127
128
  registry: getLocalRegistryURL()
128
129
  });
129
130
  let tree;
130
131
  try {
131
- await fs.access(path.join(process.cwd(), "node_modules"));
132
+ await fs.access(path.join(location, "node_modules"));
132
133
  tree = await arb.loadActual();
133
134
  }
134
135
  catch {
135
136
  tree = await arb.loadVirtual();
136
137
  }
137
138
 
138
- iterators = iter.filter(tree.edgesOut.entries(), ([, { to }]) => to !== null && !to.dev)
139
- .map(([packageName, { to }]) => deepReadEdges(packageName, { to, parent, fullLockMode, exclude }));
139
+ iterators = [
140
+ ...iter.filter(tree.edgesOut.entries(), ([, { to }]) => to !== null && (!to.dev || to.isWorkspace))
141
+ .map(([packageName, { to }]) => [packageName, to.isWorkspace ? to.target : to])
142
+ .map(([packageName, to]) => deepReadEdges(packageName, { to, parent, fullLockMode, exclude }))
143
+ ];
140
144
  }
141
145
  else {
142
146
  const configRef = { exclude, maxDepth, parent };
@@ -176,6 +180,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
176
180
  usePackageLock = false,
177
181
  fullLockMode = false,
178
182
  maxDepth,
183
+ location,
179
184
  vulnerabilityStrategy = vuln.strategies.NONE
180
185
  } = options;
181
186
 
@@ -205,7 +210,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
205
210
  const tarballLocker = new Lock({ maxConcurrent: 5 });
206
211
  tarballLocker.on("freeOne", () => logger.tick(ScannerLoggerEvents.analysis.tarball));
207
212
 
208
- const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode };
213
+ const rootDepsOptions = { maxDepth, exclude, usePackageLock, fullLockMode, location };
209
214
  for await (const currentDep of getRootDependencies(manifest, rootDepsOptions)) {
210
215
  const { name, version } = currentDep;
211
216
  const current = currentDep.exportAsPlainObject(name === manifest.name ? 0 : void 0);
@@ -246,6 +251,7 @@ export async function depWalker(manifest, options = {}, logger = new Logger()) {
246
251
 
247
252
  promisesToWait.push(scanDirOrArchive(name, version, {
248
253
  ref: current.versions[version],
254
+ location,
249
255
  tmpLocation: forceRootAnalysis && name === manifest.name ? null : tmpLocation,
250
256
  locker: tarballLocker,
251
257
  logger
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
+ }
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>;
@@ -1,185 +1,185 @@
1
- // Import NodeSecure Dependencies
2
- import * as JSXRay from "@nodesecure/js-x-ray";
3
- import { license as License } from "@nodesecure/ntlp";
4
- import * as Vuln from "@nodesecure/vuln";
5
- import { Flags } from "@nodesecure/flags";
6
-
7
- // Import Third-party Dependencies
8
- import { Maintainer } from "@npm/types";
9
-
10
- export = Scanner;
11
-
12
- declare namespace Scanner {
13
- export interface Publisher {
14
- /**
15
- * Publisher npm user name.
16
- */
17
- name: string;
18
- /**
19
- * Publisher npm user email.
20
- */
21
- email: string;
22
- /**
23
- * First version published.
24
- */
25
- version: string;
26
- /**
27
- * Date of the first publication
28
- * @example 2021-08-10T20:45:08.342Z
29
- */
30
- at: string;
31
- }
32
-
33
- export interface DependencyVersion {
34
- /** Id of the package (useful for usedBy relation) */
35
- id: number;
36
- /** By whom (id) is used the package */
37
- usedBy: Record<string, string>;
38
- /** Size on disk of the extracted tarball (in bytes) */
39
- size: number;
40
- /** Package description */
41
- description: string;
42
- /** Author of the package. This information is not trustable and can be empty. */
43
- author: Maintainer;
44
- /**
45
- * JS-X-Ray warnings
46
- *
47
- * @see https://github.com/NodeSecure/js-x-ray/blob/master/WARNINGS.md
48
- */
49
- warnings: JSXRay.Warning<JSXRay.BaseWarning>[];
50
- /** Tarball composition (files and dependencies) */
51
- composition: {
52
- /** Files extensions (.js, .md, .exe etc..) */
53
- extensions: string[];
54
- files: string[];
55
- /** Minified files (foo.min.js etc..) */
56
- minified: string[];
57
- required_files: string[];
58
- required_thirdparty: string[];
59
- required_nodejs: string[];
60
- unused: string[];
61
- missing: string[];
62
- };
63
- /**
64
- * Package licenses with SPDX expression.
65
- *
66
- * @see https://github.com/NodeSecure/licenses-conformance
67
- * @see https://github.com/NodeSecure/npm-tarball-license-parser
68
- */
69
- license: License[];
70
- /**
71
- * Flags (Array of string)
72
- *
73
- * @see https://github.com/NodeSecure/flags/blob/main/FLAGS.md
74
- */
75
- flags: Flags[];
76
- /**
77
- * If the dependency is a GIT repository
78
- */
79
- gitUrl: null | string;
80
- }
81
-
82
- export interface Dependency {
83
- /** NPM Registry metadata */
84
- metadata: {
85
- /** Count of dependencies */
86
- dependencyCount: number;
87
- /** Number of releases published on npm */
88
- publishedCount: number;
89
- lastUpdateAt: number;
90
- /** Last version SemVer */
91
- lastVersion: number;
92
- hasChangedAuthor: boolean;
93
- hasManyPublishers: boolean;
94
- hasReceivedUpdateInOneYear: boolean;
95
- /** Author of the package. This information is not trustable and can be empty. */
96
- author: Maintainer;
97
- /** Package home page */
98
- homepage: string | null;
99
- /**
100
- * List of maintainers (list of people in the organization related to the package)
101
- */
102
- maintainers: { name: string, email: string }[];
103
- /**
104
- * List of people who published this package
105
- */
106
- publishers: Publisher[];
107
- }
108
- /** List of versions of this package available in the dependency tree (In the payload) */
109
- versions: Record<string, DependencyVersion>;
110
- /**
111
- * Vulnerabilities fetched dependending on the selected vulnerabilityStrategy
112
- *
113
- * @see https://github.com/NodeSecure/vuln
114
- */
115
- vulnerabilities: Vuln.Strategy.StandardVulnerability[];
116
- }
117
-
118
- export type GlobalWarning = string[];
119
- export type Dependencies = Record<string, Dependency>;
120
-
121
- export interface Payload {
122
- /** Payload unique id */
123
- id: string;
124
- /** Name of the analyzed package */
125
- rootDependencyName: string;
126
- /** Global warnings list */
127
- warnings: GlobalWarning[];
128
- /** All the dependencies of the package (flattened) */
129
- dependencies: Dependencies;
130
- /** Version of the scanner used to generate the result */
131
- scannerVersion: string;
132
- /** Vulnerability strategy name (npm, snyk, node) */
133
- vulnerabilityStrategy: Vuln.Strategy.Kind;
134
- }
135
-
136
- export interface VerifyPayload {
137
- files: {
138
- list: string[];
139
- extensions: string[];
140
- minified: string[];
141
- };
142
- directorySize: number;
143
- uniqueLicenseIds: string[];
144
- licenses: License[];
145
- ast: {
146
- dependencies: Record<string, JSXRay.Dependency>;
147
- warnings: JSXRay.Warning<JSXRay.BaseWarning>[];
148
- };
149
- }
150
-
151
- export interface Options {
152
- /**
153
- * Maximum tree depth
154
- *
155
- * @default 4
156
- */
157
- readonly maxDepth?: number;
158
- /**
159
- * Use root package-lock.json. This will have the effect of triggering the Arborist package.
160
- *
161
- * @default false for from() API
162
- * @default true for cwd() API
163
- */
164
- readonly usePackageLock?: boolean;
165
- /**
166
- * Vulnerability strategy name (npm, snyk, node)
167
- *
168
- * @default NONE
169
- */
170
- readonly vulnerabilityStrategy: Vuln.Strategy.Kind;
171
- /**
172
- * Analyze root package.
173
- *
174
- * @default false for from() API
175
- * @default true for cwd() API
176
- */
177
- readonly forceRootAnalysis?: boolean;
178
- /**
179
- * Deeper dependencies analysis with cwd() API.
180
- *
181
- * @default false
182
- */
183
- readonly fullLockMode?: boolean;
184
- }
185
- }
1
+ // Import NodeSecure Dependencies
2
+ import * as JSXRay from "@nodesecure/js-x-ray";
3
+ import { license as License } from "@nodesecure/ntlp";
4
+ import * as Vuln from "@nodesecure/vuln";
5
+ import { Flags } from "@nodesecure/flags";
6
+
7
+ // Import Third-party Dependencies
8
+ import { Maintainer } from "@npm/types";
9
+
10
+ export = Scanner;
11
+
12
+ declare namespace Scanner {
13
+ export interface Publisher {
14
+ /**
15
+ * Publisher npm user name.
16
+ */
17
+ name: string;
18
+ /**
19
+ * Publisher npm user email.
20
+ */
21
+ email: string;
22
+ /**
23
+ * First version published.
24
+ */
25
+ version: string;
26
+ /**
27
+ * Date of the first publication
28
+ * @example 2021-08-10T20:45:08.342Z
29
+ */
30
+ at: string;
31
+ }
32
+
33
+ export interface DependencyVersion {
34
+ /** Id of the package (useful for usedBy relation) */
35
+ id: number;
36
+ /** By whom (id) is used the package */
37
+ usedBy: Record<string, string>;
38
+ /** Size on disk of the extracted tarball (in bytes) */
39
+ size: number;
40
+ /** Package description */
41
+ description: string;
42
+ /** Author of the package. This information is not trustable and can be empty. */
43
+ author: Maintainer;
44
+ /**
45
+ * JS-X-Ray warnings
46
+ *
47
+ * @see https://github.com/NodeSecure/js-x-ray/blob/master/WARNINGS.md
48
+ */
49
+ warnings: JSXRay.Warning<JSXRay.BaseWarning>[];
50
+ /** Tarball composition (files and dependencies) */
51
+ composition: {
52
+ /** Files extensions (.js, .md, .exe etc..) */
53
+ extensions: string[];
54
+ files: string[];
55
+ /** Minified files (foo.min.js etc..) */
56
+ minified: string[];
57
+ required_files: string[];
58
+ required_thirdparty: string[];
59
+ required_nodejs: string[];
60
+ unused: string[];
61
+ missing: string[];
62
+ };
63
+ /**
64
+ * Package licenses with SPDX expression.
65
+ *
66
+ * @see https://github.com/NodeSecure/licenses-conformance
67
+ * @see https://github.com/NodeSecure/npm-tarball-license-parser
68
+ */
69
+ license: License[];
70
+ /**
71
+ * Flags (Array of string)
72
+ *
73
+ * @see https://github.com/NodeSecure/flags/blob/main/FLAGS.md
74
+ */
75
+ flags: Flags[];
76
+ /**
77
+ * If the dependency is a GIT repository
78
+ */
79
+ gitUrl: null | string;
80
+ }
81
+
82
+ export interface Dependency {
83
+ /** NPM Registry metadata */
84
+ metadata: {
85
+ /** Count of dependencies */
86
+ dependencyCount: number;
87
+ /** Number of releases published on npm */
88
+ publishedCount: number;
89
+ lastUpdateAt: number;
90
+ /** Last version SemVer */
91
+ lastVersion: number;
92
+ hasChangedAuthor: boolean;
93
+ hasManyPublishers: boolean;
94
+ hasReceivedUpdateInOneYear: boolean;
95
+ /** Author of the package. This information is not trustable and can be empty. */
96
+ author: Maintainer;
97
+ /** Package home page */
98
+ homepage: string | null;
99
+ /**
100
+ * List of maintainers (list of people in the organization related to the package)
101
+ */
102
+ maintainers: { name: string, email: string }[];
103
+ /**
104
+ * List of people who published this package
105
+ */
106
+ publishers: Publisher[];
107
+ }
108
+ /** List of versions of this package available in the dependency tree (In the payload) */
109
+ versions: Record<string, DependencyVersion>;
110
+ /**
111
+ * Vulnerabilities fetched dependending on the selected vulnerabilityStrategy
112
+ *
113
+ * @see https://github.com/NodeSecure/vuln
114
+ */
115
+ vulnerabilities: Vuln.Strategy.StandardVulnerability[];
116
+ }
117
+
118
+ export type GlobalWarning = string[];
119
+ export type Dependencies = Record<string, Dependency>;
120
+
121
+ export interface Payload {
122
+ /** Payload unique id */
123
+ id: string;
124
+ /** Name of the analyzed package */
125
+ rootDependencyName: string;
126
+ /** Global warnings list */
127
+ warnings: GlobalWarning[];
128
+ /** All the dependencies of the package (flattened) */
129
+ dependencies: Dependencies;
130
+ /** Version of the scanner used to generate the result */
131
+ scannerVersion: string;
132
+ /** Vulnerability strategy name (npm, snyk, node) */
133
+ vulnerabilityStrategy: Vuln.Strategy.Kind;
134
+ }
135
+
136
+ export interface VerifyPayload {
137
+ files: {
138
+ list: string[];
139
+ extensions: string[];
140
+ minified: string[];
141
+ };
142
+ directorySize: number;
143
+ uniqueLicenseIds: string[];
144
+ licenses: License[];
145
+ ast: {
146
+ dependencies: Record<string, JSXRay.Dependency>;
147
+ warnings: JSXRay.Warning<JSXRay.BaseWarning>[];
148
+ };
149
+ }
150
+
151
+ export interface Options {
152
+ /**
153
+ * Maximum tree depth
154
+ *
155
+ * @default 4
156
+ */
157
+ readonly maxDepth?: number;
158
+ /**
159
+ * Use root package-lock.json. This will have the effect of triggering the Arborist package.
160
+ *
161
+ * @default false for from() API
162
+ * @default true for cwd() API
163
+ */
164
+ readonly usePackageLock?: boolean;
165
+ /**
166
+ * Vulnerability strategy name (npm, snyk, node)
167
+ *
168
+ * @default NONE
169
+ */
170
+ readonly vulnerabilityStrategy: Vuln.Strategy.Kind;
171
+ /**
172
+ * Analyze root package.
173
+ *
174
+ * @default false for from() API
175
+ * @default true for cwd() API
176
+ */
177
+ readonly forceRootAnalysis?: boolean;
178
+ /**
179
+ * Deeper dependencies analysis with cwd() API.
180
+ *
181
+ * @default false
182
+ */
183
+ readonly fullLockMode?: boolean;
184
+ }
185
+ }