From 630e6becdf3114ca57818c17b36700355ad9696e Mon Sep 17 00:00:00 2001 From: pablodiazjorge <102741971+pablodiazjorge@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:55:43 +0200 Subject: [PATCH] fix(javascript): associate requested bindings only with resolved native addons (#6402) * fix(javascript): associate requested bindings only with resolved native addons * test(javascript): cover the exact native addon association --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> --- .../javascript/ElectronBoundaryGraphNative.ts | 8 ++- .../electronApplicationAnalysis.test.ts | 52 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/application/javascript/ElectronBoundaryGraphNative.ts b/src/application/javascript/ElectronBoundaryGraphNative.ts index e15e15d19f..c23436a5dd 100644 --- a/src/application/javascript/ElectronBoundaryGraphNative.ts +++ b/src/application/javascript/ElectronBoundaryGraphNative.ts @@ -56,7 +56,13 @@ const addNativeBinding = (input: NativeBindingInput): void => { ? {} : { moduleKind: value.module_kind }), }).resolved_path; - const addon = resolved === null ? undefined : context.fileNodes.get(resolved); + // Extension probing can resolve `./addon.node` to `addon.node.js`; only a + // native-addon artifact can expose the requested binding. + const addon = + resolved === null || + context.filesByPath.get(resolved)?.kind !== "native-addon" + ? undefined + : context.fileNodes.get(resolved); const binding = context.accumulator.addNode({ kind: "native-export", identity: electronObservationIdentity( diff --git a/tests/boundary/filesystem/javascript-applications/electronApplicationAnalysis.test.ts b/tests/boundary/filesystem/javascript-applications/electronApplicationAnalysis.test.ts index d04be62ee8..bdd293e2ac 100644 --- a/tests/boundary/filesystem/javascript-applications/electronApplicationAnalysis.test.ts +++ b/tests/boundary/filesystem/javascript-applications/electronApplicationAnalysis.test.ts @@ -162,6 +162,58 @@ describe("static Electron application analysis", () => { }); }); +describe("native addon association", () => { + it.each([ + { native: false, associations: [] }, + { + native: true, + associations: [{ source: "native-addon", resolved: "addon.node" }], + }, + ])( + "associates a requested binding only with a resolved native addon (exact addon.node: $native)", + async ({ native, associations }) => { + const root = await createTestTempDirectory("rea-electron-addon-kind-"); + await Promise.all([ + writeFile( + join(root, "main.js"), + 'const addon = require("./addon.node");\nvoid addon;\n', + ), + writeFile( + join(root, "addon.node.js"), + "module.exports = { requested: true };\n", + ), + ...(native + ? [writeFile(join(root, "addon.node"), Buffer.from([0, 1, 2, 3]))] + : []), + ]); + + const result = await reconstructJavaScriptArtifact({ input_path: root }); + const graph = parseJavaScriptApplicationGraph(result.graph); + const nodes = new Map(graph.nodes.map((node) => [node.node_id, node])); + + expect(result.electron_summary).toMatchObject({ + native_addon_bindings: 1, + resolved_native_addon_bindings: native ? 1 : 0, + }); + expect( + graph.nodes.filter(({ kind }) => kind === "native-export"), + ).toHaveLength(1); + expect( + graph.edges + .filter( + (edge) => + edge.evidence.extractor.operation === + "associate-native-addon-requested-exports", + ) + .map((edge) => ({ + source: nodes.get(edge.source_node_id)?.kind, + resolved: edge.properties.resolved_path, + })), + ).toEqual(associations); + }, + ); +}); + const exactRuntimeEntrypointFixture = async (): Promise => { const root = await createTestTempDirectory("rea-electron-exact-entrypoints-"); await mkdir(join(root, "renderer/page"), { recursive: true });