Files
reverse-engineering-anything/src/domain/javascript/electronStaticAnalysis.ts
T
2c27c2a43b feat(electron): report V8 cachedData compilation boundaries (#9501)
* feat(electron): report V8 cachedData execution boundaries

analyze-javascript-application treated new vm.Script(source, {cachedData}) and vm.compileFunction(..., {cachedData}) as ordinary source. V8 deserializes previously compiled bytecode for those calls, so an Electron preload wrapper that delegates its whole contextBridge and IPC surface to a .cache/.jsc snapshot appeared to have been fully analyzed with zero exposed APIs.

Record each vm compilation with its inert cache path when the cachedData value traces to a readFileSync/readFile of path syntax, and project a cached-data execution unknown plus a loads edge from the owning source. Compilations without a cache stay recorded as no-cached-data so absence remains distinguishable from an unparsed snapshot. Count cachedData executions in the Electron summary and state the coverage limit in the graph limitations.

* style(cli): wrap the native data type path description

The `inspect-native-data-type` argument description line is 82 columns, over the repository's 80-column `.oxfmtrc.json` limit, so `npm run format:check` fails on main and on every branch merged from it. It came in with #9465 and the following commits to this file did not touch it. Reformat the one call the formatter objects to; no behaviour change.

* style(domain): reformat the pending dynamic-code accumulator field

`npm run format:check` fails on this branch, which is what `Static checks` reports: oxfmt wants the `pendingDynamicCode` union on one line. Applying it is the whole change; no behaviour is touched.

* fix(electron): preserve VM callable and cache source uncertainty

* docs(javascript): specify cached-data compilation boundaries

* fix(electron): exclude disabled caches from recovered boundaries

---------

Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 20:16:18 +08:00

64 lines
2.4 KiB
TypeScript

import * as t from "@babel/types";
import { inspectElectronBrowserNode } from "./electronStaticAnalysisBrowser.js";
import { deferElectronDynamicCode } from "./electronStaticAnalysisDynamicCode.js";
import { inspectElectronIpcNode } from "./electronStaticAnalysisIpc.js";
import { inspectElectronNativeNode } from "./electronStaticAnalysisNative.js";
import { inspectElectronProtocolNode } from "./electronStaticAnalysisProtocol.js";
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
import {
electronCandidateAuthority,
electronCalleePath,
} from "./electronStaticAnalysisValues.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
/** Collect Electron-specific syntax during the shared inert AST traversal. */
export const inspectElectronStaticNode = (
node: t.Node,
context: JavaScriptFindingContext,
): void => {
inspectElectronBrowserNode(node, context);
inspectElectronIpcNode(node, context);
inspectElectronNativeNode(node, context);
deferElectronDynamicCode(node, context);
const path =
t.isCallExpression(node) || t.isOptionalCallExpression(node)
? (electronCalleePath(node.callee, context) ??
context.electronInvocationPath?.(node))
: undefined;
const provenAppPathChange =
context.electronAppPathChangePossible?.(node) === true;
const candidateAppPathChange =
!provenAppPathChange &&
context.electronCandidateAppPathChangePossible?.(node) === true;
if (
(path !== undefined &&
path[0] === "app" &&
path[1] === "setAppPath" &&
path
.slice(2)
.every((member) => ["call", "apply", "bind"].includes(member))) ||
provenAppPathChange ||
candidateAppPathChange
)
// A bound or exported setter may escape this source before it changes the
// shared app path. Keep that possibility without claiming an invocation.
addLocatedFinding(context, {
collection: context.accumulator.appPathChanges,
key: "electron:app-path-change",
node,
value: {
...(candidateAppPathChange
? { loader_authority: "candidate" as const }
: {}),
...(t.isCallExpression(node) || t.isOptionalCallExpression(node)
? electronCandidateAuthority(node.callee, context)
: {}),
module_key: null,
location: range(node),
},
});
inspectElectronProtocolNode(node, context);
};