mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
* fix(ci): retire unused internal analysis declarations * test: use an admitted app bundle for provider routing (cherry picked from commit00aa17f12a) * test: keep bundle routing coverage outside oversized group (cherry picked from commit8658353b0d) * fix(ci): align regression fixtures with current admission contracts * test: bind extraction close fixtures to their ZIP digest (cherry picked from commit626d0250db) * test(cli): match typed analysis outcomes in function detail fixture * test(ghidra): preserve empty Java property separator framing * test(electron): retain unknown IPC ownership for native module gaps * fix(electron): share source-scoped call path identity * test(captures): assert safe source identity on SAZ refusal * test(apple): match root occurrence kind to inventory manifest * fix(examples): align directory root occurrence identity * test(electron): align equivalent fixture assertions with upstream * test(process): assert retained capture root identity * test(windows): declare the PE fixture optional header * test(ci): refresh owned registration and verifier fixtures * test(setup): use owned registrations for refresh fixtures * test(process): control unavailable ownership in CLI cleanup fixture * test(process): guard controlled compiler output arguments * test(setup): align equivalent entrypoint guards with upstream * test(process): release the cleanup target through an owned gate * test(ci): align semantic identities and owned fixture facts * test(pcap): retain the upstream DNS wire fixture branch * test(pcap): use the canonical wire ownership fixture * test(pcap): assert exact DNS wire query labels * test(ci): preserve admission and ownership fixture prerequisites * test(ci): align authenticated fixture and cleanup boundaries * test(windows): align native artifact fixtures with upstream * test(android): request complete evidence for inline MCP contracts * test: avoid pinning diagnostic wording * test: verify cleanup diagnostics at their source * test: strengthen evidence and cleanup checks * test: avoid pinning registration guidance --------- Co-authored-by: Plastic-time <Plastic-time@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
204 lines
7.0 KiB
TypeScript
204 lines
7.0 KiB
TypeScript
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
|
import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraphSchemas.js";
|
|
import type { ElectronCallPath } from "../../domain/javascript/electronStaticAnalysisTypes.js";
|
|
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
|
import { electronCallPathId } from "./ElectronCallPathIdentity.js";
|
|
import type {
|
|
ElectronIpcContext,
|
|
ElectronIpcRecord,
|
|
} from "./ElectronBoundaryAnalysis.js";
|
|
import {
|
|
addElectronInferenceEdge,
|
|
electronFindingSourceNode,
|
|
} from "./ElectronBoundaryGraphContext.js";
|
|
import {
|
|
artifactLocalIdentity,
|
|
javascriptAnalysisCoverage,
|
|
type JavaScriptArtifactGraphContext,
|
|
type JavaScriptArtifactGraphCoverage,
|
|
} from "./JavaScriptArtifactGraphContext.js";
|
|
import { staticInferenceEvidence } from "./JavaScriptArtifactGraphEvidence.js";
|
|
|
|
interface ProjectedPath {
|
|
readonly file: JavaScriptArtifactFile;
|
|
readonly path: ElectronCallPath;
|
|
readonly coverage: JavaScriptArtifactGraphCoverage;
|
|
readonly nodes: readonly ApplicationNode[];
|
|
}
|
|
|
|
/** Retain context-specific wrapper paths, then join only unique IPC records. */
|
|
export const addElectronCallPaths = (
|
|
context: JavaScriptArtifactGraphContext,
|
|
ipc: ElectronIpcContext,
|
|
): void => {
|
|
const projected: ProjectedPath[] = [];
|
|
for (const { file, javascript } of context.analysis.files) {
|
|
if (javascript === null) continue;
|
|
const coverage = javascriptAnalysisCoverage(javascript);
|
|
for (const path of javascript.electron.call_paths ?? []) {
|
|
const pathId = electronCallPathId(file.path, path);
|
|
const nodes = path.steps.map((step, index) =>
|
|
context.accumulator.addNode({
|
|
kind: "javascript-expression",
|
|
identity: artifactLocalIdentity(
|
|
file.sha256,
|
|
"electron-call-path-step",
|
|
`${pathId}:${String(index)}`,
|
|
),
|
|
observations: [
|
|
{
|
|
label: step.label,
|
|
properties: {
|
|
path_id: pathId,
|
|
entry: path.entry,
|
|
...(path.entry_path === undefined
|
|
? {}
|
|
: { entry_path: [...path.entry_path] }),
|
|
entry_kind: path.entry_kind,
|
|
channel: path.channel,
|
|
terminal: path.terminal,
|
|
step_kind: step.kind,
|
|
step_index: index,
|
|
module_key: step.module_key,
|
|
module_runtime: step.module_runtime,
|
|
conditions: path.conditions,
|
|
resolution: "candidate",
|
|
},
|
|
evidence: staticInferenceEvidence({
|
|
sha256: file.sha256,
|
|
path: file.path,
|
|
range: step.location,
|
|
operation: "substitute-electron-wrapper",
|
|
coverage,
|
|
limitations: [
|
|
"Lexical wrapper substitution is a static candidate; guards, runtime overrides and execution remain unverified.",
|
|
],
|
|
}),
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
const first = nodes[0],
|
|
entry = path.steps[0];
|
|
const owner = entry
|
|
? electronFindingSourceNode(
|
|
context,
|
|
file,
|
|
entry.module_key,
|
|
entry.module_runtime ?? undefined,
|
|
)
|
|
: undefined;
|
|
if (first && entry && owner)
|
|
addElectronInferenceEdge(context, {
|
|
source: owner,
|
|
target: first,
|
|
file,
|
|
range: entry.location,
|
|
coverage,
|
|
relation: "contains",
|
|
operation: "locate-electron-wrapper-path",
|
|
properties: {
|
|
path_id: pathId,
|
|
entry: path.entry,
|
|
...(path.entry_path === undefined
|
|
? {}
|
|
: { entry_path: [...path.entry_path] }),
|
|
resolution: "candidate",
|
|
},
|
|
});
|
|
for (let i = 1; i < nodes.length; i += 1) {
|
|
const source = nodes[i - 1],
|
|
target = nodes[i],
|
|
previous = path.steps[i - 1],
|
|
step = path.steps[i];
|
|
if (!source || !target || !previous || !step) continue;
|
|
addElectronInferenceEdge(context, {
|
|
source,
|
|
target,
|
|
file,
|
|
range: step.location,
|
|
coverage,
|
|
relation: ["exposure", "factory", "ipc"].includes(previous.kind)
|
|
? "maps_to"
|
|
: "calls",
|
|
operation: "trace-electron-wrapper-path",
|
|
properties: {
|
|
path_id: pathId,
|
|
entry: path.entry,
|
|
...(path.entry_path === undefined
|
|
? {}
|
|
: { entry_path: [...path.entry_path] }),
|
|
channel: path.channel,
|
|
resolution: "candidate",
|
|
conditions: path.conditions,
|
|
},
|
|
});
|
|
}
|
|
projected.push({ file, path, coverage, nodes });
|
|
}
|
|
}
|
|
for (const { transmission, handler } of ipc.pairings) {
|
|
const sources = projected.filter(
|
|
(p) => p.path.entry_kind === "bridge-member" && matches(p, transmission),
|
|
);
|
|
const targets = projected.filter(
|
|
(p) => p.path.entry_kind === "ipc-handler" && matches(p, handler),
|
|
);
|
|
for (const from of sources)
|
|
for (const to of targets) {
|
|
const source = from.nodes.at(-1),
|
|
target = to.nodes[0];
|
|
if (!source || !target) continue;
|
|
addElectronInferenceEdge(context, {
|
|
source,
|
|
target,
|
|
file: from.file,
|
|
range: transmission.finding.location,
|
|
coverage: from.coverage,
|
|
relation:
|
|
transmission.finding.mode === "invoke" ? "invokes" : "sends",
|
|
operation: "join-electron-wrapper-paths",
|
|
properties: {
|
|
channel: transmission.finding.channel,
|
|
pairing_basis: "unique-exact-literal-channel",
|
|
resolution: "candidate",
|
|
handler_path: to.file.path,
|
|
bridge_conditions: from.path.conditions,
|
|
handler_conditions: to.path.conditions,
|
|
},
|
|
limitations: [
|
|
"The unique static channel match does not establish runtime registration, sender acceptance, or dispatch.",
|
|
],
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const matches = (
|
|
projected: ProjectedPath,
|
|
record: ElectronIpcRecord,
|
|
): boolean => {
|
|
const { path } = projected;
|
|
const { finding } = record;
|
|
const invocation = finding.invocation_location;
|
|
const sourceStepKind =
|
|
finding.mode === "send" || finding.mode === "invoke" ? "terminal" : "ipc";
|
|
return (
|
|
projected.file.path === record.file.path &&
|
|
path.channel === finding.channel &&
|
|
path.steps.some(
|
|
(step) =>
|
|
step.kind === sourceStepKind &&
|
|
javascriptSourceRangeKey(step.location) ===
|
|
javascriptSourceRangeKey(finding.location),
|
|
) &&
|
|
(invocation === undefined ||
|
|
path.steps.some(
|
|
(step) =>
|
|
(step.kind === "call" || step.kind === "exposure") &&
|
|
javascriptSourceRangeKey(step.location) ===
|
|
javascriptSourceRangeKey(invocation),
|
|
))
|
|
);
|
|
};
|