fix(javascript): match exact empty literal channels and seeds

Pair the same empty IPC channel, context-bridge key, or endpoint value
across application versions by its exact value, and accept an exact
empty feature seed while rejecting an empty containment seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iwakurakaoru0822 2026-10-06 21:35:59 -07:00
parent 527b76ef78
commit e20da1f205
8 changed files with 136 additions and 27 deletions

View File

@ -637,9 +637,9 @@
"mcp_prompts": 6
},
"digests": {
"tools_sha256": "860472c8261d77c235f72eea3d1380a024df68efc30e095ab2a8a4856c050ec1",
"tools_sha256": "d1962c65198f036b57556918d59b2a5bdf544d2cd7c8a60ff0e4fce2efd472f1",
"prompts_sha256": "ef2de8814390b340623c32001610aa2709d90bed51537b6e562ab3abf144ab4d",
"combined_sha256": "e9e061962e5d50b1793890284c2431206950da3947211f6986ba05a60b7cd9dc",
"combined_sha256": "66307e8428ce3bddbf004e43d15a0d6b60965667cc1c1bb792b28a7b63837b74",
"providers_sha256": "7949ddcf2eb5ba41b022bb3dfa1951ab96dbf5640285fa7fd5d35b0e4a5bdab3"
}
}

View File

@ -1,5 +1,5 @@
{
"manifest_id": "ecm_0e087361a14f9b7a3d8e01dcb61d68cccb438e07b09c237568b678608856de98",
"manifest_id": "ecm_cbc35f371fbe873508281f13785667c48063cf8d9dee4e8bb8e74126baf0fba7",
"verifier": {
"id": "rea-managed-conformance",
"version": "1"
@ -13,7 +13,7 @@
"skill_digests": [
{
"skill_id": "reverse-engineer-anything",
"sha256": "d93aff007a8a5a8ade101f84d891ce969756fca753679d94b75ec8451764e8cd"
"sha256": "79a973d8c9ac217408e7f92e4a0f776b049d74dbf1d2b0c5249dab5127de893c"
}
],
"claims": [

View File

@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, fi
metadata:
version: "26"
tool_count: 126
catalog_digest: "e9e061962e5d50b1793890284c2431206950da3947211f6986ba05a60b7cd9dc"
catalog_digest: "66307e8428ce3bddbf004e43d15a0d6b60965667cc1c1bb792b28a7b63837b74"
---
# REA

View File

@ -5,7 +5,9 @@ import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { javascriptApplicationAnalysisResultSchema } from "../domain/javascriptApplicationAnalysis.js";
import { matchJavaScriptApplicationVersions } from "../domain/javascriptApplicationVersionKeys.js";
import { findApplicationFeatureSeeds } from "../domain/javascriptFeatureSeed.js";
import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js";
const analyzeSource = async (
@ -152,3 +154,74 @@ it("does not match an empty endpoint with a literal two-quote seed", async () =>
'""',
]);
});
const IPC_SOURCE =
"const { ipcMain } = require('electron'); ipcMain.handle('', () => 1); ipcMain.handle('\"\"', () => 2);";
const ipcChannels = (
nodes: Awaited<ReturnType<typeof analyzeSource>>["graph"]["nodes"],
) =>
new Map(
nodes
.filter(({ kind }) => kind === "ipc-channel")
.map((node) => [node.node_id, node.observations[0]?.properties.channel]),
);
it("selects an empty IPC channel with an exact empty seed", async () => {
const output = await analyzeSource(IPC_SOURCE);
const channels = ipcChannels(output.graph.nodes);
const matches = findApplicationFeatureSeeds(output.graph.nodes, {
kind: "channel",
value: "",
match: "exact",
case_sensitive: true,
});
expect(matches.map(({ node_id: nodeId }) => channels.get(nodeId))).toEqual([
"",
]);
});
it.each([
{ kind: "channel", value: "" },
{ kind: "string", value: "", match: "exact" },
])("accepts an exact empty seed: %j", (seed) => {
expect(
traceApplicationFeatureInputSchema.shape.seed.safeParse(seed).success,
).toBe(true);
});
it.each([
{ kind: "string", value: "" },
{ kind: "channel", value: "", match: "contains" },
{ kind: "node-id", value: "", match: "exact" },
])("rejects an empty seed that is not an exact literal: %j", (seed) => {
const parsed = traceApplicationFeatureInputSchema.shape.seed.safeParse(seed);
expect(parsed.success).toBe(false);
});
it("pairs the same empty IPC channel across application versions", async () => {
const left = await analyzeSource(IPC_SOURCE);
const right = await analyzeSource(`${IPC_SOURCE} // v2`);
const leftChannels = ipcChannels(left.graph.nodes);
const rightChannels = ipcChannels(right.graph.nodes);
const matching = matchJavaScriptApplicationVersions(
left.graph.nodes,
right.graph.nodes,
);
expect(
matching.pairs
.filter(({ left: node }) => node.kind === "ipc-channel")
.map(({ left: leftNode, right: rightNode }) => [
leftChannels.get(leftNode.node_id),
rightChannels.get(rightNode.node_id),
]),
).toEqual(
expect.arrayContaining([
["", ""],
['""', '""'],
]),
);
expect(matching.unmatchedLeft.map(({ kind }) => kind)).not.toContain(
"ipc-channel",
);
});

View File

@ -229,8 +229,11 @@ const semanticValue = (
node: ApplicationNode,
properties: Readonly<Record<string, JsonValue>>,
): string | null => {
if (node.kind === "endpoint")
return joined(properties.endpoint_kind, properties.value);
if (node.kind === "endpoint") {
const kind = nonEmptyString(properties.endpoint_kind);
const value = exactString(properties.value);
return kind === null || value === null ? null : `${kind}\0${value}`;
}
if (node.kind === "storage")
return joined(
properties.storage_kind,
@ -239,9 +242,13 @@ const semanticValue = (
if (node.kind === "ipc-channel")
return properties.resolution === "dynamic"
? null
: (nonEmptyString(properties.channel) ?? firstLabel(node));
: (exactString(properties.channel) ?? firstLabel(node));
if (node.kind === "context-bridge-api")
return firstLabel(node) ?? nonEmptyString(properties.api_name);
return (
firstLabel(node) ??
exactString(properties.api_key) ??
nonEmptyString(properties.api_name)
);
if (node.kind === "native-export")
return (
joined(
@ -293,6 +300,10 @@ const joined = (...values: readonly unknown[]): string | null => {
const nonEmptyString = (value: unknown): string | null =>
typeof value === "string" && value !== "" ? value : null;
/** A literal value such as `ipcMain.handle("")` keeps its exact empty text. */
const exactString = (value: unknown): string | null =>
typeof value === "string" ? value : null;
const stringList = (value: unknown): string[] =>
Array.isArray(value)
? value

View File

@ -1,6 +1,9 @@
import { compareCodePoints } from "./canonicalOrdering.js";
import type { ApplicationNode } from "./javascriptApplicationGraph.js";
import type { ApplicationFeatureSeed } from "./javascriptFeatureTraceSchemas.js";
import {
featureSeedMatchMode,
type ApplicationFeatureSeed,
} from "./javascriptFeatureTraceSchemas.js";
import type { JsonValue } from "./jsonValue.js";
/** One explicit field that matched a feature seed. */
@ -49,7 +52,7 @@ const seedMatch = (
]
: [];
if (!kindMatches(node, seed.kind)) return [];
const mode = seed.match ?? (seed.kind === "string" ? "contains" : "exact");
const mode = featureSeedMatchMode(seed);
const query = normalized(seed.value, seed.case_sensitive);
const match = candidateFields(node, seed.kind).find((candidate) => {
const value = normalized(candidate.value, seed.case_sensitive);

View File

@ -10,21 +10,43 @@ const nodeIdSchema = prefixedDigestSchema("jag_node");
const edgeIdSchema = prefixedDigestSchema("jag_edge");
const boundedTextSchema = z.string().min(1);
/** Literal starting point for one application feature trace. */
const applicationFeatureSeedSchema = z.strictObject({
kind: z.enum([
"node-id",
"route",
"string",
"api",
"channel",
"module",
"native-export",
]),
value: boundedTextSchema,
match: z.enum(["exact", "contains"]).nullable().default(null),
case_sensitive: z.boolean().default(false),
});
/** Matching mode for a literal seed: string seeds default to containment. */
export const featureSeedMatchMode = (seed: {
readonly kind: string;
readonly match: "exact" | "contains" | null;
}): "exact" | "contains" =>
seed.match ?? (seed.kind === "string" ? "contains" : "exact");
/**
* Literal starting point for one application feature trace. An exact literal
* seed may be empty, such as the channel in `ipcMain.handle("")`; an empty
* containment seed would select every node, so it is rejected.
*/
const applicationFeatureSeedSchema = z
.strictObject({
kind: z.enum([
"node-id",
"route",
"string",
"api",
"channel",
"module",
"native-export",
]),
value: z.string(),
match: z.enum(["exact", "contains"]).nullable().default(null),
case_sensitive: z.boolean().default(false),
})
.superRefine((seed, context) => {
if (seed.value !== "") return;
if (seed.kind === "node-id" || featureSeedMatchMode(seed) === "contains")
context.addIssue({
code: "custom",
path: ["value"],
message:
'An empty seed value requires an exact literal match: use match "exact" with a non-node-id kind',
});
});
/** Evidence-backed application graph and feature seed. */
export const traceApplicationFeatureInputSchema = z

File diff suppressed because one or more lines are too long