Merge pull request #835 from kaoru0822-kitauji/fix/electron-computed-member-keys

fix(javascript): preserve computed Electron member uncertainty
This commit is contained in:
Kaoru0822 2026-10-06 20:51:31 -07:00 committed by GitHub
commit 3a4a63f70a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 3 deletions

View File

@ -14,8 +14,8 @@ import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
import {
argumentNode,
calleeName,
propertyName,
range,
semanticStaticPropertyName,
} from "./javascriptStaticAnalysisHelpers.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
@ -124,7 +124,10 @@ const inspectValidationCall = (
!t.isOptionalMemberExpression(node.callee)
)
return;
const method = propertyName(node.callee.property);
const method = semanticStaticPropertyName(
node.callee.property,
node.callee.computed,
);
if (!["startsWith", "endsWith", "includes"].includes(method)) return;
const subject = t.isNode(node.callee.object)
? senderSubject(node.callee.object)

View File

@ -9,6 +9,7 @@ import {
calleeName,
propertyName,
range,
semanticStaticPropertyName,
stringValue,
} from "./javascriptStaticAnalysisHelpers.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
@ -161,7 +162,7 @@ const nativeRequire = (
? nativeRequire(node.object)
: undefined;
if (nested === undefined) return undefined;
const member = propertyName(node.property);
const member = semanticStaticPropertyName(node.property, node.computed);
return { ...nested, member: member === "" ? nested.member : member };
}
if (!t.isCallExpression(node)) return undefined;

View File

@ -1,3 +1,6 @@
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@ -64,6 +67,28 @@ describe("static Electron application analysis", () => {
});
});
it("keeps computed identifier member keys unknown", async () => {
const root = await computedMemberFixtureDirectory();
const result = await reconstructJavaScriptArtifact({ input_path: root });
const graph = parseJavaScriptApplicationGraph(result.graph);
const requestedMembers = graph.nodes.flatMap((node) =>
node.kind === "native-export"
? node.observations.flatMap(({ properties }) => {
const members = properties.requested_members;
return Array.isArray(members) ? members : [];
})
: [],
);
expect(result.electron_summary).toMatchObject({
sender_validation_observations: 0,
native_addon_bindings: 1,
resolved_native_addon_bindings: 1,
});
expect(requestedMembers).toEqual(["*"]);
});
it("returns a tagged cancellation without executing application code", async () => {
const root = await fixtureDirectory();
const controller = new AbortController();
@ -273,3 +298,24 @@ const fixtureDirectory = async (): Promise<string> => {
await writeElectronBoundaryFixture(root);
return root;
};
const computedMemberFixtureDirectory = async (): Promise<string> => {
const root = await createTestTempDirectory("rea-electron-computed-members-");
await mkdir(join(root, "native"));
await Promise.all([
writeFile(
join(root, "main.js"),
String.raw`
const member = "read";
const selected = require("./native/addon.node")[member];
const startsWith = "includes";
function validate(event) {
return event.senderFrame.url[startsWith]("file://");
}
module.exports = { selected, validate };
`,
),
writeFile(join(root, "native", "addon.node"), Buffer.from([0, 1, 2, 3])),
]);
return root;
};