UNPKG

@nodesecure/js-x-ray

Version:
145 lines 4.02 kB
// Import Internal Dependencies import { getCallExpressionIdentifier, getMemberExpressionIdentifier } from "../estree/index.js"; import { generateWarning } from "../warnings.js"; // CONSTANTS export const JS_TYPES = new Set([ "AggregateError", "Array", "ArrayBuffer", "BigInt", "BigInt64Array", "BigUint64Array", "Boolean", "DataView", "Date", "Error", "EvalError", "FinalizationRegistry", "Float32Array", "Float64Array", "Function", "Int16Array", "Int32Array", "Int8Array", "Map", "Number", "Object", "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp", "Set", "SharedArrayBuffer", "String", "Symbol", "SyntaxError", "TypeError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", "URIError", "WeakMap", "WeakRef", "WeakSet" ]); /** * @description Search for monkey patching of built-in prototypes. * @example * Array.prototype.map = function() {}; */ function validateNodeAssignment(node, ctx) { if (node.type !== "AssignmentExpression" || node.left.type !== "MemberExpression") { return [false]; } return validateMemberExpression(node.left, ctx); } function resolveDefinePropertyIdentifier(node, ctx) { const id = getCallExpressionIdentifier(node); if (id === "Object.defineProperty" || id === "Reflect.defineProperty") { return id; } if (id === null || !id.includes(".")) { return null; } const [objectPart, ...rest] = id.split("."); const methodName = rest.join("."); if (methodName !== "defineProperty") { return null; } const resolved = resolveJsTypeName(objectPart, ctx); if (resolved === "Object" || resolved === "Reflect") { return `${resolved}.defineProperty`; } return null; } function validateDefineProperty(node, ctx) { if (node.type !== "CallExpression") { return [false]; } const resolvedId = resolveDefinePropertyIdentifier(node, ctx); if (resolvedId === null) { return [false]; } // TODO: detect aliased prototype target in defineProperty, // e.g. const ap = Array.prototype; Object.defineProperty(ap, ...) const firstArg = node.arguments.at(0); if (firstArg?.type !== "MemberExpression") { return [false]; } return validateMemberExpression(firstArg, ctx); } function resolveJsTypeName(name, ctx) { if (JS_TYPES.has(name)) { return name; } const tracedData = ctx.sourceFile.tracer.getDataFromIdentifier(name); if (tracedData !== null && JS_TYPES.has(tracedData.identifierOrMemberExpr)) { return tracedData.identifierOrMemberExpr; } return null; } function validateMemberExpression(node, ctx) { const iter = getMemberExpressionIdentifier(node, { externalIdentifierLookup: (name) => ctx.sourceFile.tracer.literalIdentifiers.get(name)?.value ?? null }); const rawName = iter.next().value; if (typeof rawName !== "string") { return [false]; } const jsTypeName = resolveJsTypeName(rawName, ctx); if (jsTypeName === null) { return [false]; } return [ iter.next().value === "prototype", `${jsTypeName}.prototype` ]; } function initialize(ctx) { const { tracer } = ctx.sourceFile; for (const jsType of JS_TYPES) { tracer.trace(jsType, { followConsecutiveAssignment: true }); } } function main(node, options) { const { sourceFile, data: prototypeName } = options; sourceFile.warnings.push(generateWarning("monkey-patch", { value: prototypeName, location: node.loc })); } export default { name: "isMonkeyPatch", nodeTypes: ["AssignmentExpression", "CallExpression"], validateNode: [ validateNodeAssignment, validateDefineProperty ], main, initialize, breakOnMatch: false }; //# sourceMappingURL=isMonkeyPatch.js.map