Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: trace process.getBuiltinModule #294

Merged
merged 1 commit into from
Aug 16, 2024
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
10 changes: 8 additions & 2 deletions src/probes/isRequire/isRequire.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -135,8 +137,12 @@ function main(node, options) {

export default {
name: "isRequire",
validateNode: [validateNodeRequire, validateNodeEvalRequire],
validateNode: [
validateNodeRequire,
validateNodeEvalRequire
],
main,
teardown,
breakOnMatch: true,
breakGroup: "import"
};
21 changes: 21 additions & 0 deletions test/probes/isRequire.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 10 additions & 2 deletions workspaces/estree-ast-utils/src/utils/VariableTracer.d.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
}

Expand Down
40 changes: 37 additions & 3 deletions workspaces/estree-ast-utils/src/utils/VariableTracer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);

Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
});
});
Loading