@nodesecure/js-x-ray
Version:
JavaScript AST XRay analysis
136 lines • 5.01 kB
JavaScript
var _a;
// Import Node.js Dependencies
import assert from "node:assert";
// Import Internal Dependencies
import isUnsafeCallee from "./probes/isUnsafeCallee.js";
import isLiteral from "./probes/isLiteral.js";
import isLiteralRegex from "./probes/isLiteralRegex.js";
import isRegexObject from "./probes/isRegexObject.js";
import isRequire from "./probes/isRequire/isRequire.js";
import isImportDeclaration from "./probes/isImportDeclaration.js";
import isWeakCrypto from "./probes/isWeakCrypto.js";
import isBinaryExpression from "./probes/isBinaryExpression.js";
import isArrayExpression from "./probes/isArrayExpression.js";
import isESMExport from "./probes/isESMExport.js";
import isFetch from "./probes/isFetch.js";
import isUnsafeCommand from "./probes/isUnsafeCommand.js";
import isSyncIO from "./probes/isSyncIO.js";
import isSerializeEnv from "./probes/isSerializeEnv.js";
import dataExfiltration from "./probes/data-exfiltration.js";
// CONSTANTS
const kProbeOriginalContext = Symbol.for("ProbeOriginalContext");
export class ProbeRunner {
probes;
sourceFile;
static Signals = Object.freeze({
Break: Symbol.for("breakWalk"),
Skip: Symbol.for("skipWalk"),
Continue: null
});
/**
* Note:
* The order of the table has an importance/impact on the correct execution of the probes
*/
static Defaults = [
isFetch,
isRequire,
isESMExport,
isUnsafeCallee,
isLiteral,
isLiteralRegex,
isRegexObject,
isImportDeclaration,
isWeakCrypto,
isBinaryExpression,
isArrayExpression,
isUnsafeCommand,
isSerializeEnv,
dataExfiltration
];
static Optionals = {
"synchronous-io": isSyncIO
};
constructor(sourceFile, probes = _a.Defaults) {
this.sourceFile = sourceFile;
for (const probe of probes) {
assert(typeof probe.validateNode === "function" || Array.isArray(probe.validateNode), `Invalid probe ${probe.name}: validateNode must be a function or an array of functions`);
assert(typeof probe.main === "function", `Invalid probe ${probe.name}: main must be a function`);
assert(typeof probe.initialize === "function" || probe.initialize === undefined, `Invalid probe ${probe.name}: initialize must be a function or undefined`);
if (probe.initialize) {
const isDefined = Reflect.defineProperty(probe, kProbeOriginalContext, {
enumerable: false,
value: structuredClone(probe.context),
configurable: true
});
if (!isDefined) {
throw new Error(`Failed to define original context for probe '${probe.name}'`);
}
const context = probe.initialize(this.#getProbeContext(probe));
if (context) {
probe.context = structuredClone(context);
}
}
}
this.probes = probes;
}
#getProbeContext(probe) {
return {
sourceFile: this.sourceFile,
context: probe.context
};
}
#runProbe(probe, node) {
const validationFns = Array.isArray(probe.validateNode) ?
probe.validateNode : [probe.validateNode];
const ctx = this.#getProbeContext(probe);
for (const validateNode of validationFns) {
const [isMatching, data = null] = validateNode(node, ctx);
if (isMatching) {
return probe.main(node, {
...ctx,
signals: _a.Signals,
data
});
}
}
return null;
}
walk(node) {
const breakGroups = new Set();
for (const probe of this.probes) {
if (probe.breakGroup && breakGroups.has(probe.breakGroup)) {
continue;
}
try {
const signal = this.#runProbe(probe, node);
if (signal === _a.Signals.Continue) {
continue;
}
if (signal === _a.Signals.Skip) {
return "skip";
}
if (signal === _a.Signals.Break || probe.breakOnMatch) {
const breakGroup = probe.breakGroup || null;
if (breakGroup === null) {
break;
}
else {
breakGroups.add(breakGroup);
}
}
}
finally {
probe.teardown?.(this.#getProbeContext(probe));
}
}
return null;
}
finalize() {
for (const probe of this.probes) {
probe.finalize?.(this.#getProbeContext(probe));
probe.context = probe[kProbeOriginalContext];
}
}
}
_a = ProbeRunner;
//# sourceMappingURL=ProbeRunner.js.map