From ae9237c696e6cade947cf3ab254a0301772f3370 Mon Sep 17 00:00:00 2001 From: fraxken Date: Fri, 16 Aug 2024 17:46:19 +0200 Subject: [PATCH] feat: trace process.getBuiltinModule --- src/probes/isRequire/isRequire.js | 10 ++++- test/probes/isRequire.spec.js | 21 ++++++++++ .../src/utils/VariableTracer.d.ts | 12 +++++- .../src/utils/VariableTracer.js | 40 +++++++++++++++++-- .../test/VariableTracer/assignments.spec.js | 39 ++++++++++++++++++ 5 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/probes/isRequire/isRequire.js b/src/probes/isRequire/isRequire.js index 83b44ee..a88be1a 100644 --- a/src/probes/isRequire/isRequire.js +++ b/src/probes/isRequire/isRequire.js @@ -17,7 +17,9 @@ function validateNodeRequire(node, { tracer }) { return [false]; } - const data = tracer.getDataFromIdentifier(id); + const data = tracer.getDataFromIdentifier(id, { + removeGlobalIdentifier: true + }); return [ data !== null && data.name === "require", @@ -135,8 +137,12 @@ function main(node, options) { export default { name: "isRequire", - validateNode: [validateNodeRequire, validateNodeEvalRequire], + validateNode: [ + validateNodeRequire, + validateNodeEvalRequire + ], main, + teardown, breakOnMatch: true, breakGroup: "import" }; diff --git a/test/probes/isRequire.spec.js b/test/probes/isRequire.spec.js index c68b88e..dcde204 100644 --- a/test/probes/isRequire.spec.js +++ b/test/probes/isRequire.spec.js @@ -46,6 +46,27 @@ test("it should execute probe using process.mainModule.require (detected by the assert.ok(dependencies.has("http")); }); +test("it should execute probe using process.getBuiltinModule (detected by the VariableTracer)", () => { + const str = ` + if (globalThis.process?.getBuiltinModule) { + const fs = globalThis.process.getBuiltinModule('fs'); + const module = globalThis.process.getBuiltinModule('module'); + const require = module.createRequire(import.meta.url); + const foo = require('foo'); + } + `; + const ast = parseScript(str); + const sastAnalysis = getSastAnalysis(str, isRequire) + .execute(ast.body); + + assert.strictEqual(sastAnalysis.warnings().length, 0); + const dependencies = sastAnalysis.dependencies(); + assert.deepEqual( + [...dependencies.keys()], + ["fs", "module", "foo"] + ); +}); + test("it should execute probe on a variable reassignments of require (detected by the VariableTracer)", () => { const str = ` const r = require; diff --git a/workspaces/estree-ast-utils/src/utils/VariableTracer.d.ts b/workspaces/estree-ast-utils/src/utils/VariableTracer.d.ts index c2f61fe..66c3c97 100644 --- a/workspaces/estree-ast-utils/src/utils/VariableTracer.d.ts +++ b/workspaces/estree-ast-utils/src/utils/VariableTracer.d.ts @@ -1,6 +1,13 @@ // Import Node.js Dependencies import EventEmitter from "node:events"; +export interface DataIdentifierOptions { + /** + * @default false + */ + removeGlobalIdentifier?: boolean; +} + declare class VariableTracer extends EventEmitter { static AssignmentEvent: Symbol; @@ -14,11 +21,12 @@ declare class VariableTracer extends EventEmitter { moduleName?: string; name?: string; }): VariableTracer; - getDataFromIdentifier(identifierOrMemberExpr: string): null | { + removeGlobalIdentifier(identifierOrMemberExpr: string): string; + getDataFromIdentifier(identifierOrMemberExpr: string, options: DataIdentifierOptions): null | { name: string; identifierOrMemberExpr: string; assignmentMemory: string[]; - } + }; walk(node: any): void; } diff --git a/workspaces/estree-ast-utils/src/utils/VariableTracer.js b/workspaces/estree-ast-utils/src/utils/VariableTracer.js index 93673b9..4b03600 100644 --- a/workspaces/estree-ast-utils/src/utils/VariableTracer.js +++ b/workspaces/estree-ast-utils/src/utils/VariableTracer.js @@ -13,10 +13,18 @@ import { extractLogicalExpression } from "../extractLogicalExpression.js"; // CONSTANTS const kGlobalIdentifiersToTrace = new Set([ - "global", "globalThis", "root", "GLOBAL", "window" + "globalThis", + "global", + "root", + "GLOBAL", + "window" ]); const kRequirePatterns = new Set([ - "require", "require.resolve", "require.main", "process.mainModule.require" + "require", + "require.resolve", + "require.main", + "process.mainModule.require", + "process.getBuiltinModule" ]); const kUnsafeGlobalCallExpression = new Set(["eval", "Function"]); @@ -96,7 +104,33 @@ export class VariableTracer extends EventEmitter { /** * @param {!string} identifierOrMemberExpr An identifier like "foo" or "foo.bar" */ - getDataFromIdentifier(identifierOrMemberExpr) { + removeGlobalIdentifier(identifierOrMemberExpr) { + if (!identifierOrMemberExpr.includes(".")) { + return identifierOrMemberExpr; + } + + const globalIdentifier = [...kGlobalIdentifiersToTrace] + .find((globalId) => identifierOrMemberExpr.startsWith(globalId)); + + return globalIdentifier ? + identifierOrMemberExpr.slice(globalIdentifier.length + 1) : + identifierOrMemberExpr; + } + + /** + * @param {!string} identifierOrMemberExpr An identifier like "foo" or "foo.bar" + * @param {object} [options={}] + */ + getDataFromIdentifier( + identifierOrMemberExpr, + options = {} + ) { + const { removeGlobalIdentifier = false } = options; + if (removeGlobalIdentifier) { + // eslint-disable-next-line no-param-reassign + identifierOrMemberExpr = this.removeGlobalIdentifier(identifierOrMemberExpr); + } + const isMemberExpr = identifierOrMemberExpr.includes("."); const isTracingIdentifier = this.#traced.has(identifierOrMemberExpr); diff --git a/workspaces/estree-ast-utils/test/VariableTracer/assignments.spec.js b/workspaces/estree-ast-utils/test/VariableTracer/assignments.spec.js index 90586f7..b0c64ee 100644 --- a/workspaces/estree-ast-utils/test/VariableTracer/assignments.spec.js +++ b/workspaces/estree-ast-utils/test/VariableTracer/assignments.spec.js @@ -144,3 +144,42 @@ test("it should be able to Trace a global assignment using a LogicalExpression", assert.strictEqual(eventOne.identifierOrMemberExpr, "require"); assert.strictEqual(eventOne.id, "foo"); }); + +test("it should be able to Trace assignment of process.getBuiltinModule", () => { + const helpers = createTracer(true); + const assignments = helpers.getAssignmentArray(); + + helpers.walkOnCode(` + if (globalThis.process?.getBuiltinModule) { + const foo = globalThis.process.getBuiltinModule; + const fs = foo('fs'); + } + `); + + const foo = helpers.tracer.getDataFromIdentifier("foo"); + assert.deepEqual(foo, { + name: "require", + identifierOrMemberExpr: "process.getBuiltinModule", + assignmentMemory: ["foo"] + }); + assert.strictEqual(assignments.length, 1); + + const [eventOne] = assignments; + assert.strictEqual(eventOne.identifierOrMemberExpr, "process.getBuiltinModule"); + assert.strictEqual(eventOne.id, "foo"); + + assert.strictEqual( + helpers.tracer.getDataFromIdentifier("globalThis.process.getBuiltinModule"), + null + ); + + const getBuiltinModule = helpers.tracer.getDataFromIdentifier( + "globalThis.process.getBuiltinModule", + { removeGlobalIdentifier: true } + ); + assert.deepEqual(getBuiltinModule, { + name: "require", + identifierOrMemberExpr: "process.getBuiltinModule", + assignmentMemory: ["foo"] + }); +});