@nodesecure/tarball
Version:
NodeSecure tarball scanner
109 lines • 4.17 kB
JavaScript
// Import Node.js Dependencies
import path from "node:path";
// Import Third-party Dependencies
import * as conformance from "@nodesecure/conformance";
import { ManifestManager } from "@nodesecure/mama";
import { AstAnalyser, DefaultCollectableSet, warnings, TsSourceParser } from "@nodesecure/js-x-ray";
// Import Internal Dependencies
import { SourceCodeReport, SourceCodeScanner } from "./SourceCodeScanner.class.js";
import { getTarballComposition } from "../utils/index.js";
import { DnsResolver } from "./DnsResolver.class.js";
export class NpmTarball {
static JS_EXTENSIONS = new Set([
".js", ".mjs", ".cjs",
".ts", ".mts", ".cts",
".jsx", ".tsx"
]);
manifest;
#resolver;
#astAnalyser;
constructor(mama, options = {}) {
if (!ManifestManager.isLocated(mama)) {
throw new Error("ManifestManager must have a location");
}
this.manifest = mama;
this.#resolver = options?.resolver ?? new DnsResolver();
this.#astAnalyser = options?.astAnalyser ?? null;
}
async scanFiles(astAnalyserOptions, options = {}) {
const { exclude = [] } = options;
const location = this.manifest.location;
const [composition, spdx] = await Promise.all([
getTarballComposition(location),
conformance.extractLicenses(location)
]);
let code;
if (composition.files.length === 1 && composition.files.includes("package.json")) {
code = new SourceCodeReport();
}
else {
const options = this.#optionsWithHostnameSet(astAnalyserOptions ?? {});
const astAnalyser = this.#astAnalyser ?? new AstAnalyser(options);
const hostNameSet = astAnalyser.getCollectableSet("hostname");
code = await new SourceCodeScanner(this.manifest, { astAnalyser }).iterate({
manifest: [...this.manifest.getEntryFiles()]
.flatMap(filterJavaScriptFiles(exclude)),
javascript: composition.files
.flatMap(filterJavaScriptFiles(exclude))
});
if (hostNameSet instanceof DefaultCollectableSet) {
const operationQueue = Array.from(hostNameSet)
.map(({ value, locations }) => this.#resolver.isPrivateHost(value)
.then((isPrivate) => {
if (isPrivate) {
locations.forEach(({ file, location }) => {
code.warnings.push({
kind: "shady-link",
...warnings["shady-link"],
file: file ?? undefined,
location,
value,
source: "Scanner"
});
});
}
}));
await Promise.allSettled(operationQueue);
}
}
return {
conformance: spdx,
composition,
code
};
}
#optionsWithHostnameSet(options) {
const hasHostnameSet = options?.collectables?.some((collectable) => collectable.type === "hostname");
if (hasHostnameSet) {
return options;
}
return {
...options,
collectables: [
...options.collectables ?? [],
new DefaultCollectableSet("hostname")
]
};
}
}
function filterJavaScriptFiles(exclude = []) {
return (file) => {
// Exclude .d.ts files
if (file.includes("d.ts")) {
return [];
}
// Exclude files matching any glob pattern
if (exclude.some((pattern) => path.matchesGlob(file, pattern))) {
return [];
}
const fileExt = path.extname(file);
if (NpmTarball.JS_EXTENSIONS.has(fileExt)) {
return file;
}
if (TsSourceParser.FileExtensions.has(fileExt)) {
return file;
}
return [];
};
}
//# sourceMappingURL=NpmTarball.class.js.map