Skip to content

Commit fad019f

Browse files
authored
feat(ProbeRunner): implement context (#395)
Update workspaces/js-x-ray/src/probes/isSyncIO.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> chore: add changeset
1 parent 8300966 commit fad019f

File tree

9 files changed

+203
-58
lines changed

9 files changed

+203
-58
lines changed

.changeset/quiet-files-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/js-x-ray": minor
3+
---
4+
5+
Implement context for Probe and ProbeRunner

workspaces/js-x-ray/docs/AstAnalyser.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ Below a basic probe that detect a string assignation to `danger`:
143143
export const customProbes = [
144144
{
145145
name: "customProbeUnsafeDanger",
146-
validateNode: (node, sourceFile) => [
146+
validateNode: (node) => [
147147
node.type === "VariableDeclaration" && node.declarations[0].init.value === "danger"
148148
],
149-
main: (node, options) => {
150-
const { sourceFile, data: calleeName } = options;
149+
main: (node, ctx) => {
150+
const { sourceFile, data: calleeName } = ctx;
151151
if (node.declarations[0].init.value === "danger") {
152152
sourceFile.warnings.push({
153153
kind: "unsafe-danger",

workspaces/js-x-ray/src/ProbeRunner.ts

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,29 @@ import type { SourceFile } from "./SourceFile.js";
2424
import type { OptionalWarningName } from "./warnings.js";
2525

2626
export type ProbeReturn = void | null | symbol;
27-
export type ProbeInitializeCallback = (sourceFile: SourceFile) => void;
28-
export type ProbeFinalizeCallback = (sourceFile: SourceFile) => void;
29-
export type ProbeMainCallback = (
30-
node: any,
31-
options: { sourceFile: SourceFile; data?: any; }
32-
) => ProbeReturn;
33-
export type ProbeTeardownCallback = (options: { sourceFile: SourceFile; }) => void;
34-
export type ProbeValidationCallback = (node: ESTree.Node, sourceFile: SourceFile) => [boolean, any?];
35-
36-
export interface Probe {
27+
export type ProbeContextDef = Record<string, any>;
28+
export type ProbeContext<T extends ProbeContextDef = ProbeContextDef> = {
29+
sourceFile: SourceFile;
30+
context?: T;
31+
};
32+
33+
export type ProbeValidationCallback<T extends ProbeContextDef = ProbeContextDef> = (
34+
node: ESTree.Node, ctx: ProbeContext<T>
35+
) => [boolean, any?];
36+
37+
export interface Probe<T extends ProbeContextDef = ProbeContextDef> {
3738
name: string;
38-
initialize?: ProbeInitializeCallback;
39-
finalize?: ProbeFinalizeCallback;
40-
validateNode: ProbeValidationCallback | ProbeValidationCallback[];
41-
main: ProbeMainCallback;
42-
teardown?: ProbeTeardownCallback;
39+
initialize?: (ctx: ProbeContext<T>) => void | ProbeContext;
40+
finalize?: (ctx: ProbeContext<T>) => void;
41+
validateNode: ProbeValidationCallback<T> | ProbeValidationCallback<T>[];
42+
main: (
43+
node: any,
44+
ctx: ProbeContext<T> & { data?: any; }
45+
) => ProbeReturn;
46+
teardown?: (ctx: ProbeContext<T>) => void;
4347
breakOnMatch?: boolean;
4448
breakGroup?: string;
49+
context?: ProbeContext<T>;
4550
}
4651

4752
export const ProbeSignals = Object.freeze({
@@ -97,28 +102,42 @@ export class ProbeRunner {
97102
`Invalid probe ${probe.name}: initialize must be a function or undefined`
98103
);
99104
if (probe.initialize) {
100-
probe.initialize(sourceFile);
105+
const context = probe.initialize(this.#getProbeContext(probe));
106+
if (context) {
107+
probe.context = context;
108+
}
101109
}
102110
}
103111

104112
this.probes = probes;
105113
}
106114

115+
#getProbeContext(
116+
probe: Probe
117+
): ProbeContext {
118+
return {
119+
sourceFile: this.sourceFile,
120+
context: probe.context
121+
};
122+
}
123+
107124
#runProbe(
108125
probe: Probe,
109126
node: ESTree.Node
110127
): ProbeReturn {
111128
const validationFns = Array.isArray(probe.validateNode) ?
112129
probe.validateNode : [probe.validateNode];
130+
const ctx = this.#getProbeContext(probe);
131+
113132
for (const validateNode of validationFns) {
114133
const [isMatching, data = null] = validateNode(
115134
node,
116-
this.sourceFile
135+
ctx
117136
);
118137

119138
if (isMatching) {
120139
return probe.main(node, {
121-
sourceFile: this.sourceFile,
140+
...ctx,
122141
data
123142
});
124143
}
@@ -158,9 +177,7 @@ export class ProbeRunner {
158177
}
159178
}
160179
finally {
161-
if (probe.teardown) {
162-
probe.teardown({ sourceFile: this.sourceFile });
163-
}
180+
probe.teardown?.(this.#getProbeContext(probe));
164181
}
165182
}
166183

@@ -169,9 +186,7 @@ export class ProbeRunner {
169186

170187
finalize(): void {
171188
for (const probe of this.probes) {
172-
if (probe.finalize) {
173-
probe.finalize(this.sourceFile);
174-
}
189+
probe.finalize?.(this.#getProbeContext(probe));
175190
}
176191
}
177192
}

workspaces/js-x-ray/src/probes/isFetch.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { getCallExpressionIdentifier } from "@nodesecure/estree-ast-utils";
33
import type { ESTree } from "meriyah";
44

55
// Import Internal Dependencies
6-
import { SourceFile } from "../SourceFile.js";
6+
import type { ProbeContext } from "../ProbeRunner.js";
77

88
function validateNode(
99
node: ESTree.Node,
10-
{ tracer }: SourceFile
10+
ctx: ProbeContext
1111
): [boolean, any?] {
12+
const { tracer } = ctx.sourceFile;
1213
const id = getCallExpressionIdentifier(node);
1314

1415
if (id === null) {
@@ -20,13 +21,17 @@ function validateNode(
2021
return [data !== null && data.identifierOrMemberExpr === "fetch"];
2122
}
2223

23-
function initialize(sourceFile: SourceFile) {
24+
function initialize(
25+
ctx: ProbeContext
26+
) {
27+
const { sourceFile } = ctx;
28+
2429
sourceFile.tracer.trace("fetch", { followConsecutiveAssignment: true });
2530
}
2631

2732
function main(
2833
_node: ESTree.Node,
29-
{ sourceFile }: { sourceFile: SourceFile; }
34+
{ sourceFile }: ProbeContext
3035
) {
3136
sourceFile.flags.add("fetch");
3237
}

workspaces/js-x-ray/src/probes/isRequire/isRequire.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import {
1010
import type { ESTree } from "meriyah";
1111

1212
// Import Internal Dependencies
13-
import { ProbeSignals } from "../../ProbeRunner.js";
14-
import { SourceFile } from "../../SourceFile.js";
13+
import { ProbeSignals, type ProbeContext } from "../../ProbeRunner.js";
1514
import { isLiteral } from "../../types/estree.js";
1615
import { RequireCallExpressionWalker } from "./RequireCallExpressionWalker.js";
1716
import { generateWarning } from "../../warnings.js";
1817

1918
function validateNodeRequire(
2019
node: ESTree.Node,
21-
{ tracer }: SourceFile
20+
ctx: ProbeContext
2221
): [boolean, any?] {
22+
const { tracer } = ctx.sourceFile;
2323
const id = getCallExpressionIdentifier(node, {
2424
resolveCallExpression: false
2525
});
@@ -63,14 +63,14 @@ function validateNodeEvalRequire(
6363
}
6464

6565
function teardown(
66-
{ sourceFile }: { sourceFile: SourceFile; }
66+
ctx: ProbeContext
6767
) {
68-
sourceFile.dependencyAutoWarning = false;
68+
ctx.sourceFile.dependencyAutoWarning = false;
6969
}
7070

7171
function main(
7272
node: ESTree.CallExpression,
73-
options: { sourceFile: SourceFile; data?: string; }
73+
options: ProbeContext & { data?: string; }
7474
) {
7575
const { sourceFile, data: calleeName } = options;
7676
const { tracer } = sourceFile;

workspaces/js-x-ray/src/probes/isSerializeEnv.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {
66
import type { ESTree } from "meriyah";
77

88
// Import Internal Dependencies
9-
import { SourceFile } from "../SourceFile.js";
109
import { generateWarning } from "../warnings.js";
11-
import { ProbeSignals } from "../ProbeRunner.js";
10+
import { ProbeSignals, type ProbeContext } from "../ProbeRunner.js";
1211

1312
/**
1413
* @description Detect serialization of process.env which could indicate environment variable exfiltration
@@ -20,8 +19,10 @@ import { ProbeSignals } from "../ProbeRunner.js";
2019
*/
2120
function validateNode(
2221
node: ESTree.Node,
23-
{ tracer }: SourceFile
22+
ctx: ProbeContext
2423
): [boolean, any?] {
24+
const { tracer } = ctx.sourceFile;
25+
2526
const id = getCallExpressionIdentifier(node);
2627

2728
if (id === null) {
@@ -58,9 +59,9 @@ function validateNode(
5859

5960
function main(
6061
node: ESTree.Node,
61-
options: { sourceFile: SourceFile; }
62+
ctx: ProbeContext
6263
) {
63-
const { sourceFile } = options;
64+
const { sourceFile } = ctx;
6465

6566
const warning = generateWarning("serialize-environment", {
6667
value: "JSON.stringify(process.env)",
@@ -72,8 +73,10 @@ function main(
7273
}
7374

7475
function initialize(
75-
{ tracer }: SourceFile
76+
ctx: ProbeContext
7677
) {
78+
const { tracer } = ctx.sourceFile;
79+
7780
tracer
7881
.trace("process.env", {
7982
followConsecutiveAssignment: true

workspaces/js-x-ray/src/probes/isSyncIO.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getCallExpressionIdentifier } from "@nodesecure/estree-ast-utils";
33
import type { ESTree } from "meriyah";
44

55
// Import Internal Dependencies
6-
import { SourceFile } from "../SourceFile.js";
6+
import type { ProbeContext } from "../ProbeRunner.js";
77
import { generateWarning } from "../warnings.js";
88

99
// CONSTANTS
@@ -40,8 +40,9 @@ const kSyncIOIdentifierOrMemberExps = [
4040

4141
function validateNode(
4242
node: ESTree.Node,
43-
{ tracer }: SourceFile
43+
ctx: ProbeContext
4444
): [boolean, any?] {
45+
const { tracer } = ctx.sourceFile;
4546
const id = getCallExpressionIdentifier(
4647
node,
4748
{
@@ -64,12 +65,12 @@ function validateNode(
6465
}
6566

6667
function initialize(
67-
sourceFile: SourceFile
68+
ctx: ProbeContext
6869
) {
6970
kSyncIOIdentifierOrMemberExps.forEach((identifierOrMemberExp) => {
7071
const moduleName = identifierOrMemberExp.split(".")[0];
7172

72-
return sourceFile.tracer.trace(identifierOrMemberExp, {
73+
ctx.sourceFile.tracer.trace(identifierOrMemberExp, {
7374
followConsecutiveAssignment: true,
7475
moduleName
7576
});
@@ -78,13 +79,13 @@ function initialize(
7879

7980
function main(
8081
node: ESTree.CallExpression,
81-
{ sourceFile }: { sourceFile: SourceFile; }
82+
ctx: ProbeContext
8283
) {
8384
const warning = generateWarning("synchronous-io", {
8485
value: node.callee.name,
8586
location: node.loc
8687
});
87-
sourceFile.warnings.push(warning);
88+
ctx.sourceFile.warnings.push(warning);
8889
}
8990

9091
export default {

workspaces/js-x-ray/src/probes/isWeakCrypto.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getCallExpressionIdentifier } from "@nodesecure/estree-ast-utils";
33
import type { ESTree } from "meriyah";
44

55
// Import Internal Dependencies
6-
import { SourceFile } from "../SourceFile.js";
6+
import type { ProbeContext } from "../ProbeRunner.js";
77
import { generateWarning } from "../warnings.js";
88
import {
99
isLiteral
@@ -20,9 +20,9 @@ const kWeakAlgorithms = new Set([
2020

2121
function validateNode(
2222
node: ESTree.Node,
23-
sourceFile: SourceFile
23+
ctx: ProbeContext
2424
): [boolean, any?] {
25-
const { tracer } = sourceFile;
25+
const { tracer } = ctx.sourceFile;
2626

2727
const id = getCallExpressionIdentifier(node);
2828
if (id === null || !tracer.importedModules.has("crypto")) {
@@ -37,18 +37,21 @@ function validateNode(
3737
}
3838

3939
function initialize(
40-
sourceFile: SourceFile
40+
ctx: ProbeContext
4141
) {
42-
sourceFile.tracer.trace("crypto.createHash", {
42+
const { tracer } = ctx.sourceFile;
43+
44+
tracer.trace("crypto.createHash", {
4345
followConsecutiveAssignment: true,
4446
moduleName: "crypto"
4547
});
4648
}
4749

4850
function main(
4951
node: ESTree.CallExpression,
50-
{ sourceFile }: { sourceFile: SourceFile; }
52+
ctx: ProbeContext
5153
) {
54+
const { sourceFile } = ctx;
5255
const arg = node.arguments.at(0);
5356

5457
if (isLiteral(arg) && kWeakAlgorithms.has(arg.value)) {

0 commit comments

Comments
 (0)