@nodesecure/tarball
Version:
NodeSecure tarball scanner
265 lines • 8.87 kB
JavaScript
// Import Node.js Dependencies
import path from "node:path";
// Import Third-party Dependencies
import { ManifestManager, parseNpmSpec } from "@nodesecure/mama";
import {} from "@nodesecure/js-x-ray";
export const NODE_BUILTINS = new Set([
"assert",
"assert/strict",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"https",
"module",
"net",
"os",
"smalloc",
"path",
"path/posix",
"path/win32",
"punycode",
"querystring",
"readline",
"readline/promises",
"repl",
"stream",
"stream/web",
"stream/promises",
"stream/consumers",
"_stream_duplex",
"_stream_passthrough",
"_stream_readable",
"_stream_transform",
"_stream_writable",
"_stream_wrap",
"string_decoder",
"sys",
"timers",
"timers/promises",
"tls",
"tty",
"url",
"util",
"util/types",
"vm",
"zlib",
"freelist",
"v8",
"v8/tools/arguments",
"v8/tools/codemap",
"v8/tools/consarray",
"v8/tools/csvparser",
"v8/tools/logreader",
"v8/tools/profile_view",
"v8/tools/splaytree",
"process",
"inspector",
"inspector/promises",
"async_hooks",
"http2",
"perf_hooks",
"trace_events",
"worker_threads",
"node:test",
"test/reporters",
"test/mock_loader",
"node:sea",
"node:sqlite",
"wasi",
"diagnostics_channel"
]);
const kFileExtensions = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".node", ".json"];
const kExternalModules = new Set(["http", "https", "net", "http2", "dgram", "child_process"]);
const kExternalThirdPartyDeps = new Set([
"undici",
"node-fetch",
"execa",
"cross-spawn",
"got",
"axios",
"ky",
"superagent",
"cross-fetch"
]);
const kRelativeImportPath = new Set([".", "..", "./", "../"]);
export class DependencyCollectableSet {
type = "dependency";
dependencies = Object.create(null);
#values = new Set();
#files = new Set();
#dependenciesInTryBlock = new Set();
#subpathImportsDependencies = {};
#thirdPartyDependencies = new Set();
#thirdPartyAliasedDependencies = new Set();
#missingDependencies = new Set();
#nodeDependencies = new Set();
#mama;
#hasExternalCapacity = false;
constructor(mama) {
this.#mama = mama;
}
extract() {
const unusedDependencies = this.#difference(this.#mama.dependencies.filter((name) => !name.startsWith("@types")), [...this.#thirdPartyDependencies, ...this.#thirdPartyAliasedDependencies]);
const hasMissingOrUnusedDependency = unusedDependencies.length > 0 ||
this.#missingDependencies.size > 0;
return {
files: this.#files,
dependenciesInTryBlock: [...this.#dependenciesInTryBlock],
dependencies: {
nodeJs: [...this.#nodeDependencies],
thirdparty: [...this.#thirdPartyDependencies],
subpathImports: this.#subpathImportsDependencies,
unused: unusedDependencies,
missing: [...this.#missingDependencies]
},
flags: {
hasExternalCapacity: this.#hasExternalCapacity,
hasMissingOrUnusedDependency
}
};
}
add(value, { metadata, location }) {
if (!metadata) {
return;
}
const relativeFile = metadata.relativeFile;
if (!(relativeFile in this.dependencies)) {
this.dependencies[relativeFile] = Object.create(null);
}
this.dependencies[relativeFile][value] = {
unsafe: Boolean(metadata?.unsafe),
inTry: Boolean(metadata?.inTry),
location
};
if (metadata?.inTry) {
this.#dependenciesInTryBlock.add(value);
}
const filtered = this.#filerDependencyByKind(value, path.dirname(relativeFile));
if (filtered.file) {
this.#files.add(filtered.file);
}
if (filtered.package) {
this.#analyzeDependency(filtered.package, Boolean(metadata?.inTry));
}
this.#values.add(value);
}
#filerDependencyByKind(dependency, relativeFileLocation) {
const firstChar = dependency.charAt(0);
/**
* @example
* require("..");
* require("/home/marco/foo.js");
*/
if (firstChar === "." || firstChar === "/") {
// Note: condition only possible for CJS
if (kRelativeImportPath.has(dependency)) {
return { file: path.join(dependency, "index.js") };
}
// Note: we are speculating that the extension is .js (but it could be .json or .node)
const fixedFileName = path.extname(dependency) === "" ?
`${dependency}.js` : dependency;
return { file: path.join(relativeFileLocation, fixedFileName) };
}
return { package: dependency };
}
#analyzeDependency(sourceDependency, inTry) {
if (this.#values.has(sourceDependency)) {
return;
}
const { dependencies, devDependencies, nodejsImports = {} } = this.#mama;
let thirdPartyAliasedDependency;
// See: https://nodejs.org/api/packages.html#subpath-imports
if (this.#isAliasFileModule(sourceDependency) && sourceDependency in nodejsImports) {
const [alias, importEntry] = this.#buildSubpathDependency(sourceDependency, nodejsImports);
this.#subpathImportsDependencies[alias] = importEntry;
if (!this.#isFile(importEntry)) {
this.#thirdPartyAliasedDependencies.add(importEntry);
thirdPartyAliasedDependency = importEntry;
}
}
const name = this.#extractDependencyName(sourceDependency, dependencies);
let thirdPartyDependency;
if (!this.#isFile(name) &&
!this.#isCoreModule(name) &&
!devDependencies.includes(name)
&& !inTry) {
thirdPartyDependency = name;
this.#thirdPartyDependencies.add(name);
}
if (thirdPartyDependency &&
this.#isMissingDependency(thirdPartyDependency, thirdPartyAliasedDependency)) {
this.#missingDependencies.add(thirdPartyDependency);
}
let isNodeDependency = false;
if (this.#isCoreModule(sourceDependency)) {
this.#nodeDependencies.add(sourceDependency);
isNodeDependency = true;
}
if (this.#hasExternalCapacity) {
return;
}
if (((isNodeDependency && kExternalModules.has(sourceDependency))
|| (thirdPartyDependency && kExternalThirdPartyDeps.has(thirdPartyDependency)))) {
this.#hasExternalCapacity = true;
}
}
#extractDependencyName(sourceDependency, dependencies) {
for (const dependency of dependencies) {
if (dependency === sourceDependency) {
return sourceDependency;
}
if (sourceDependency.startsWith(dependency)) {
return dependency;
}
}
return parseNpmSpec(sourceDependency)?.name ?? sourceDependency;
}
#isMissingDependency(thirdPartyDependency, thirdPartyAliasedDependency) {
const { dependencies, nodejsImports = {} } = this.#mama;
return !dependencies.includes(thirdPartyDependency) &&
!(thirdPartyDependency in nodejsImports) &&
thirdPartyDependency !== thirdPartyAliasedDependency;
}
#difference(arr1, arr2) {
return arr1.filter((item) => !arr2.includes(item));
}
#isFile(filePath) {
return filePath.startsWith(".")
|| kFileExtensions.some((extension) => filePath.endsWith(extension));
}
#isCoreModule(moduleName) {
const cleanModuleName = moduleName.startsWith("node:") ? moduleName.slice(5) : moduleName;
// Note: We need to also check moduleName because builtins package only return true for 'node:test'.
return NODE_BUILTINS.has(cleanModuleName) || NODE_BUILTINS.has(moduleName);
}
#isAliasFileModule(moduleName) {
return moduleName.charAt(0) === "#";
}
#buildSubpathDependency(alias, nodeImports) {
const importEntry = nodeImports[alias];
return typeof importEntry === "string" ?
[alias, importEntry] :
[alias, "node" in importEntry ? importEntry.node : importEntry.default];
}
values() {
return this.#values;
}
toJSON() {
return {
type: this.type,
entries: []
};
}
}
//# sourceMappingURL=DependencyCollectableSet.class.js.map