Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-moose-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nodesecure/js-x-ray": minor
---

Move Signals into probe.main context
4 changes: 2 additions & 2 deletions workspaces/js-x-ray/docs/AstAnalyser.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const customProbes = [
node.type === "VariableDeclaration" && node.declarations[0].init.value === "danger"
],
main: (node, ctx) => {
const { sourceFile, data: calleeName } = ctx;
const { sourceFile, data: calleeName, signals } = ctx;
if (node.declarations[0].init.value === "danger") {
sourceFile.warnings.push({
kind: "unsafe-danger",
Expand All @@ -158,7 +158,7 @@ export const customProbes = [
severity: "Warning"
});

return ProbeSignals.Skip;
return signals.Skip;
}

return null;
Expand Down
26 changes: 16 additions & 10 deletions workspaces/js-x-ray/src/ProbeRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export type ProbeContext<T extends ProbeContextDef = ProbeContextDef> = {
sourceFile: SourceFile;
context?: T;
};
export type ProbeMainContext<T extends ProbeContextDef = ProbeContextDef> = ProbeContext<T> & {
data?: any;
signals: typeof ProbeRunner.Signals;
};

export type ProbeValidationCallback<T extends ProbeContextDef = ProbeContextDef> = (
node: ESTree.Node, ctx: ProbeContext<T>
Expand All @@ -41,23 +45,24 @@ export interface Probe<T extends ProbeContextDef = ProbeContextDef> {
validateNode: ProbeValidationCallback<T> | ProbeValidationCallback<T>[];
main: (
node: any,
ctx: ProbeContext<T> & { data?: any; }
ctx: ProbeMainContext<T>
) => ProbeReturn;
teardown?: (ctx: ProbeContext<T>) => void;
breakOnMatch?: boolean;
breakGroup?: string;
context?: ProbeContext<T>;
}

export const ProbeSignals = Object.freeze({
Break: Symbol.for("breakWalk"),
Skip: Symbol.for("skipWalk")
});

export class ProbeRunner {
probes: Probe[];
sourceFile: 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
Expand Down Expand Up @@ -138,6 +143,7 @@ export class ProbeRunner {
if (isMatching) {
return probe.main(node, {
...ctx,
signals: ProbeRunner.Signals,
data
});
}
Expand All @@ -157,15 +163,15 @@ export class ProbeRunner {
}

try {
const result = this.#runProbe(probe, node);
if (result === null) {
const signal = this.#runProbe(probe, node);
if (signal === ProbeRunner.Signals.Continue) {
continue;
}

if (result === ProbeSignals.Skip) {
if (signal === ProbeRunner.Signals.Skip) {
return "skip";
}
if (result === ProbeSignals.Break || probe.breakOnMatch) {
if (signal === ProbeRunner.Signals.Break || probe.breakOnMatch) {
const breakGroup = probe.breakGroup || null;

if (breakGroup === null) {
Expand Down
8 changes: 4 additions & 4 deletions workspaces/js-x-ray/src/probes/isRequire/isRequire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import type { ESTree } from "meriyah";

// Import Internal Dependencies
import { ProbeSignals, type ProbeContext } from "../../ProbeRunner.js";
import type { ProbeContext, ProbeMainContext } from "../../ProbeRunner.js";
import { isLiteral } from "../../types/estree.js";
import { RequireCallExpressionWalker } from "./RequireCallExpressionWalker.js";
import { generateWarning } from "../../warnings.js";
Expand Down Expand Up @@ -70,9 +70,9 @@ function teardown(

function main(
node: ESTree.CallExpression,
options: ProbeContext & { data?: string; }
ctx: ProbeMainContext
) {
const { sourceFile, data: calleeName } = options;
const { sourceFile, data: calleeName, signals } = ctx;
const { tracer } = sourceFile;

if (node.arguments.length === 0) {
Expand Down Expand Up @@ -170,7 +170,7 @@ function main(
}

// We skip walking the tree to avoid anymore warnings...
return ProbeSignals.Skip;
return signals.Skip;
}

default:
Expand Down
11 changes: 7 additions & 4 deletions workspaces/js-x-ray/src/probes/isSerializeEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import type { ESTree } from "meriyah";

// Import Internal Dependencies
import { generateWarning } from "../warnings.js";
import { ProbeSignals, type ProbeContext } from "../ProbeRunner.js";
import type {
ProbeContext,
ProbeMainContext
} from "../ProbeRunner.js";

/**
* @description Detect serialization of process.env which could indicate environment variable exfiltration
Expand Down Expand Up @@ -59,17 +62,17 @@ function validateNode(

function main(
node: ESTree.Node,
ctx: ProbeContext
ctx: ProbeMainContext
) {
const { sourceFile } = ctx;
const { sourceFile, signals } = ctx;

const warning = generateWarning("serialize-environment", {
value: "JSON.stringify(process.env)",
location: node.loc
});
sourceFile.warnings.push(warning);

return ProbeSignals.Skip;
return signals.Skip;
}

function initialize(
Expand Down
13 changes: 6 additions & 7 deletions workspaces/js-x-ray/src/probes/isUnsafeCallee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import type { ESTree } from "meriyah";
import { getCallExpressionIdentifier } from "@nodesecure/estree-ast-utils";

// Import Internal Dependencies
import { SourceFile } from "../SourceFile.js";
import { generateWarning } from "../warnings.js";
import { ProbeSignals } from "../ProbeRunner.js";
import type { ProbeMainContext } from "../ProbeRunner.js";

/**
* @description Detect unsafe statement
Expand All @@ -21,19 +20,19 @@ function validateNode(

function main(
node: ESTree.CallExpression,
options: { sourceFile: SourceFile; data?: string; }
ctx: ProbeMainContext
) {
const { sourceFile, data: calleeName } = options;
const { sourceFile, data: calleeName, signals } = ctx;

if (!calleeName) {
return ProbeSignals.Skip;
return signals.Skip;
}
if (
calleeName === "Function" &&
node.callee.arguments.length > 0 &&
node.callee.arguments[0].value === "return this"
) {
return ProbeSignals.Skip;
return signals.Skip;
}

const warning = generateWarning("unsafe-stmt", {
Expand All @@ -42,7 +41,7 @@ function main(
});
sourceFile.warnings.push(warning);

return ProbeSignals.Skip;
return signals.Skip;
}

function isEvalCallee(
Expand Down
14 changes: 8 additions & 6 deletions workspaces/js-x-ray/src/probes/isUnsafeCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import type { ESTree } from "meriyah";

// Import Internal Dependencies
import { SourceFile } from "../SourceFile.js";
import { generateWarning } from "../warnings.js";
import { ProbeSignals } from "../ProbeRunner.js";
import { isLiteral, isTemplateLiteral } from "../types/estree.js";
import {
isLiteral,
isTemplateLiteral
} from "../types/estree.js";
import type { ProbeMainContext } from "../ProbeRunner.js";

// CONSTANTS
const kUnsafeCommands = ["csrutil", "uname", "ping", "curl"];
Expand Down Expand Up @@ -102,9 +104,9 @@ function validateNode(

function main(
node: ESTree.CallExpression,
options: { sourceFile: SourceFile; data?: string; }
ctx: ProbeMainContext
) {
const { sourceFile, data: methodName } = options;
const { sourceFile, data: methodName, signals } = ctx;

const commandArg = node.arguments[0];
if (!isLiteral(commandArg) && !isTemplateLiteral(commandArg)) {
Expand Down Expand Up @@ -134,7 +136,7 @@ function main(
});
sourceFile.warnings.push(warning);

return ProbeSignals.Skip;
return signals.Skip;
}

return null;
Expand Down
25 changes: 12 additions & 13 deletions workspaces/js-x-ray/test/ProbeRunner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import type { ESTree } from "meriyah";

// Import Internal Dependencies
import {
ProbeRunner,
ProbeSignals
ProbeRunner
} from "../src/ProbeRunner.js";
import { SourceFile } from "../src/SourceFile.js";

Expand Down Expand Up @@ -113,7 +112,7 @@ describe("ProbeRunner", () => {

assert.strictEqual(fakeProbe.main.mock.calls.length, 1);
assert.deepEqual(fakeProbe.main.mock.calls.at(0)?.arguments, [
astNode, { sourceFile, data: null, context: undefined }
astNode, { sourceFile, data: null, context: undefined, signals: ProbeRunner.Signals }
]);

assert.strictEqual(fakeProbe.teardown.mock.calls.length, 1);
Expand All @@ -126,7 +125,7 @@ describe("ProbeRunner", () => {
const data = { test: "data" };
const fakeProbe = {
validateNode: mock.fn((_: ESTree.Node) => [true, data]),
main: mock.fn(() => ProbeSignals.Skip)
main: mock.fn(() => ProbeRunner.Signals.Skip)
};

const sourceFile = new SourceFile();
Expand All @@ -149,14 +148,14 @@ describe("ProbeRunner", () => {
astNode, expectedContext
]);
assert.deepEqual(fakeProbe.main.mock.calls.at(0)?.arguments, [
astNode, { ...expectedContext, data }
astNode, { ...expectedContext, data, signals: ProbeRunner.Signals }
]);
});

it("should trigger and return a skip signal", () => {
const fakeProbe = {
validateNode: (node: ESTree.Node) => [node.type === "Literal"],
main: () => ProbeSignals.Skip,
main: () => ProbeRunner.Signals.Skip,
teardown: mock.fn()
};

Expand All @@ -181,20 +180,20 @@ describe("ProbeRunner", () => {
it("should call the finalize methods", () => {
const fakeProbe = {
validateNode: (_: ESTree.Node) => [true],
main: () => ProbeSignals.Skip,
main: () => ProbeRunner.Signals.Skip,
finalize: mock.fn()
};

const fakeProbeSkip = {
validateNode: (_: ESTree.Node) => [true],
main: () => ProbeSignals.Skip,
main: () => ProbeRunner.Signals.Skip,
teardown: mock.fn(),
finalize: mock.fn()
};

const fakeProbeBreak = {
validateNode: (_: ESTree.Node) => [true],
main: () => ProbeSignals.Break,
main: () => ProbeRunner.Signals.Break,
teardown: mock.fn(),
finalize: mock.fn()
};
Expand Down Expand Up @@ -227,7 +226,7 @@ describe("ProbeRunner", () => {
const fakeProbe = {
initialize: mock.fn(() => fakeCtx),
validateNode: mock.fn((_: ESTree.Node) => [true]),
main: mock.fn(() => ProbeSignals.Skip),
main: mock.fn(() => ProbeRunner.Signals.Skip),
finalize: mock.fn()
};

Expand All @@ -251,7 +250,7 @@ describe("ProbeRunner", () => {
astNode, expectedContext
]);
assert.deepEqual(fakeProbe.main.mock.calls.at(0)?.arguments, [
astNode, { ...expectedContext, data: null }
astNode, { ...expectedContext, data: null, signals: ProbeRunner.Signals }
]);
assert.deepEqual(fakeProbe.initialize.mock.calls.at(0)?.arguments, [
{ sourceFile, context: undefined }
Expand All @@ -266,7 +265,7 @@ describe("ProbeRunner", () => {
const fakeProbe = {
initialize: mock.fn(),
validateNode: mock.fn((_: ESTree.Node) => [true]),
main: mock.fn(() => ProbeSignals.Skip),
main: mock.fn(() => ProbeRunner.Signals.Skip),
finalize: mock.fn(),
context: fakeCtx
};
Expand All @@ -291,7 +290,7 @@ describe("ProbeRunner", () => {
astNode, expectedContext
]);
assert.deepEqual(fakeProbe.main.mock.calls.at(0)?.arguments, [
astNode, { ...expectedContext, data: null }
astNode, { ...expectedContext, data: null, signals: ProbeRunner.Signals }
]);
assert.deepEqual(fakeProbe.finalize.mock.calls.at(0)?.arguments, [
expectedContext
Expand Down
7 changes: 3 additions & 4 deletions workspaces/js-x-ray/test/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from "../../src/index.js";
import {
ProbeRunner,
ProbeSignals,
type Probe
} from "../../src/ProbeRunner.js";

Expand Down Expand Up @@ -82,8 +81,8 @@ export const customProbes: Probe[] = [
node.declarations[0].init.value === "danger"
];
},
main(node, options) {
const { sourceFile, data: calleeName } = options;
main(node, ctx) {
const { sourceFile, data: calleeName, signals } = ctx;
if (node.declarations[0].init.value === "danger") {
sourceFile.warnings.push({
kind: "unsafe-danger",
Expand All @@ -94,7 +93,7 @@ export const customProbes: Probe[] = [
severity: "Warning"
});

return ProbeSignals.Skip;
return signals.Skip;
}

return null;
Expand Down