chore: merge main into feat/dyld-shared-cache

This commit is contained in:
morluto committed 2026-10-08 21:26:50 +08:00
commit 03bbecd941
10 files changed
+277 -12

No files matched your search

+2 -2
View File
@@ -718,9 +718,9 @@
"mcp_prompts": 6
},
"digests": {
"tools_sha256": "ef0bc1222f6236c035a576f4ba1317ed3a838d2052f672106cc00c07632298b3",
"tools_sha256": "8e69e2a116fc08a18958869f9a8266e5dca980dece2a42748089f1240a603c7e",
"prompts_sha256": "ef2de8814390b340623c32001610aa2709d90bed51537b6e562ab3abf144ab4d",
"combined_sha256": "63f9e1c6471f3e88bcf5b0faa66717499e9c2e07d9cef9289f8947c4f2c32e41",
"combined_sha256": "f1dd4e4a6913f65967dfa15daeeaee26994cecf06d94ae6b3bfff1047657f634",
"providers_sha256": "bd826b2f7aa112a8d9ad83fe5c7e3a46534aae56c71399cda72413ce62f5e554"
}
}
@@ -1,5 +1,5 @@
{
"manifest_id": "ecm_9347855ec2e9c25646961e61d955e25774a2c4142e52dd9fc31b1abd272aa615",
"manifest_id": "ecm_4b6bff63b1c4bbcb5b228437c78d2a65050d20cbf314b6bbcceada66b5a410d4",
"verifier": {
"id": "rea-managed-conformance",
"version": "1"
@@ -13,7 +13,7 @@
"skill_digests": [
{
"skill_id": "reverse-engineer-anything",
"sha256": "9b9765cc8f5a1d761f9d9fa21f6cbafd5ca1a2e0e7f83f706b5b72c903bf8c3a"
"sha256": "677806b859d650d63b160f788f2816eaa7f1b6fec3637b954c3af7f0fc9b9f59"
}
],
"claims": [
+1 -1
View File
@@ -4,7 +4,7 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, fi
metadata:
version: "33"
tool_count: 140
catalog_digest: "63f9e1c6471f3e88bcf5b0faa66717499e9c2e07d9cef9289f8947c4f2c32e41"
catalog_digest: "f1dd4e4a6913f65967dfa15daeeaee26994cecf06d94ae6b3bfff1047657f634"
---
# REA
+7
View File
@@ -1,4 +1,5 @@
import { readFile } from "node:fs/promises";
import { z } from "zod";
import { AnalysisError } from "../../domain/analysisErrorBase.js";
import { AnalysisInputError } from "../../domain/analysisErrorCore.js";
@@ -113,6 +114,12 @@ const parseCaptureEvidence = (input: unknown) => {
try {
evidence = parseEvidence(input);
} catch (cause: unknown) {
if (cause instanceof z.ZodError)
throw new AnalysisInputError(
"compare_process_captures",
{ cause },
projectInputIssues(cause.issues, input),
);
throw invalidCaptureEvidence();
}
if (
+27
View File
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { MAX_JSON_DEPTH } from "../jsonValue.js";
import { projectKeyedArchive } from "./keyedArchive.js";
const deepValue = (depth: number): unknown => {
let value: unknown = 1;
for (let index = 0; index < depth; index += 1) value = { nested: value };
return value;
};
describe("projectKeyedArchive input bound", () => {
it("rejects an over-deep archive value with a validation error instead of crashing", () => {
let error: unknown;
try {
projectKeyedArchive(deepValue(MAX_JSON_DEPTH + 1), {
offset: 0,
limit: 1,
});
} catch (caught) {
error = caught;
}
expect(error).toBeInstanceOf(z.ZodError);
expect(error).not.toBeInstanceOf(RangeError);
});
});
+25
View File
@@ -4,6 +4,7 @@ import { describe, expect } from "vitest";
import { createAnalysisProfile } from "./analysisProfile.js";
import type { BinaryTarget } from "./binaryTarget.js";
import { createEvidence, evidenceSchema, parseEvidence } from "./evidence.js";
import { MAX_JSON_DEPTH } from "./jsonValue.js";
import { createEvidenceBundle } from "./evidenceBundle.js";
const TARGET: BinaryTarget = {
@@ -255,3 +256,27 @@ describe("DOS analysis evidence identity", () => {
);
});
});
describe("evidence parameter depth bound", () => {
it("rejects evidence whose parameters nest past the JSON depth limit", () => {
const evidence = createEvidence(TARGET, PROVIDER, {
operation: "health",
parameters: {},
result: true,
});
let value: unknown = 1;
for (let index = 0; index <= MAX_JSON_DEPTH; index += 1)
value = { nested: value };
const result = evidenceSchema.safeParse({
...evidence,
parameters: { attack: value },
});
expect(result.success).toBe(false);
if (result.success) return;
expect(
result.error.issues.some((issue) =>
issue.message.includes("maximum nesting depth"),
),
).toBe(true);
});
});
+66 -2
View File
@@ -1,8 +1,12 @@
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { jsonObjectSchema, jsonValueSchema } from "./jsonValue.js";
import {
jsonObjectSchema,
jsonValueSchema,
MAX_JSON_DEPTH,
} from "./jsonValue.js";
const values = [
"text",
@@ -35,3 +39,63 @@ it("still parses only JSON values at runtime", () => {
for (const value of [undefined, Number.NaN, Infinity, () => 0, [undefined]])
expect(jsonValueSchema.safeParse(value).success).toBe(false);
});
const deepObject = (depth: number): unknown => {
let value: unknown = 1;
for (let index = 0; index < depth; index += 1) value = { nested: value };
return value;
};
describe("jsonValueSchema depth bound", () => {
it("accepts values nested to the maximum depth", () => {
expect(jsonValueSchema.safeParse(deepObject(MAX_JSON_DEPTH)).success).toBe(
true,
);
});
it("rejects values nested past the maximum depth without throwing", () => {
const result = jsonValueSchema.safeParse(deepObject(MAX_JSON_DEPTH + 1));
expect(result.success).toBe(false);
if (result.success) return;
expect(result.error.issues[0]?.message).toContain("maximum nesting depth");
});
it.each(["object", "array"])(
"rejects the original 10000-level %s crash without throwing",
(kind) => {
let value: unknown = 1;
for (let index = 0; index < 10_000; index += 1)
value = kind === "object" ? { nested: value } : [value];
const result = jsonValueSchema.safeParse(value);
expect(result.success).toBe(false);
if (result.success) return;
expect(result.error.issues[0]?.message).toContain(
"maximum nesting depth",
);
},
);
it("rejects cyclic values instead of recursing forever", () => {
const cyclic: Record<string, unknown> = {};
cyclic.self = cyclic;
const result = jsonValueSchema.safeParse(cyclic);
expect(result.success).toBe(false);
});
it("keeps json semantics for non-JSON values", () => {
expect(jsonValueSchema.safeParse(undefined).success).toBe(false);
expect(jsonValueSchema.safeParse({ a: Number.NaN }).success).toBe(false);
expect(
jsonValueSchema.safeParse({ a: Number.POSITIVE_INFINITY }).success,
).toBe(false);
expect(jsonValueSchema.safeParse({ a: 1n }).success).toBe(false);
});
it("accepts ordinary nested JSON", () => {
expect(
jsonValueSchema.safeParse({
list: [1, "two", null, true, { deep: { further: [{}] } }],
}).success,
).toBe(true);
});
});
+56 -2
View File
@@ -1,10 +1,64 @@
import { z } from "zod";
/** JSON-safe value shared by domain, application, and adapter boundaries. */
export const jsonValueSchema = z.json();
/**
* Maximum nesting depth accepted for JSON values.
*
* Sits well below the point where the recursive `z.json()` core overflows its
* stack (measured between 2000 and 3000 on Node 24) while leaving room for
* legitimately deep target-derived trees such as compiled NIB view hierarchies.
*/
export const MAX_JSON_DEPTH = 1000;
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const exceedsMaxDepth = (root: unknown): boolean => {
// Depth counts edges from the root.
const pending: { value: unknown; depth: number }[] = [
{ value: root, depth: 0 },
];
while (pending.length > 0) {
const item = pending.pop();
if (item === undefined) break;
if (item.depth > MAX_JSON_DEPTH) return true;
const value = item.value;
if (Array.isArray(value)) {
for (const child of value)
pending.push({ value: child, depth: item.depth + 1 });
} else if (isRecord(value)) {
for (const key of Object.keys(value))
pending.push({ value: value[key], depth: item.depth + 1 });
}
}
return false;
};
/**
* JSON-safe value shared by domain, application, and adapter boundaries.
*
* An iterative depth guard runs before the recursive `z.json()` core so
* hostile nested payloads surface as validation issues instead of RangeErrors.
* The guard only refuses over-deep values; ordinary JSON parsing semantics
* (including which members `z.json()` keeps) are unchanged.
*/
const jsonCore = z.json();
// Every JSON value satisfies `{}`. Advertise that instead of Zod's
// self-referential `$defs` projection, which MCP clients and model APIs that
// reject recursive JSON Schemas refuse. Runtime parsing still admits only JSON.
// The override sits on the core as well as on the pipe because the standard
// JSON Schema projection walks into pipe stages without consulting the outer
// node, while the public toJSONSchema projection stops at the pipe.
jsonCore._zod.toJSONSchema = () => ({});
export const jsonValueSchema = z
.unknown()
.superRefine((value, context) => {
if (exceedsMaxDepth(value))
context.addIssue({
code: "custom",
message: `JSON value exceeds maximum nesting depth of ${MAX_JSON_DEPTH}`,
});
})
.pipe(jsonCore);
jsonValueSchema._zod.toJSONSchema = () => ({});
/** JSON object boundary for caller-visible parameter maps. */
+55 -3
View File
@@ -16,6 +16,7 @@ import {
} from "../../../src/application/process/ProcessCli.js";
import { createEvidence } from "../../../src/domain/evidence.js";
import { PROCESS_PROVIDER } from "../../../src/application/process/ProcessEvidence.js";
import { INVESTIGATION_EXAMPLES } from "../../../src/contracts/investigationExamples.js";
const roots: string[] = [];
const execFileAsync = promisify(execFile);
@@ -292,11 +293,62 @@ describe("process CLI evidence validation", () => {
const malformed = join(root, "malformed-evidence.json");
await writeFile(malformed, "{}");
expect(await compareProcessEvidenceFiles(malformed, malformed)).toEqual({
expect(
await compareProcessEvidenceFiles(malformed, malformed),
).toMatchObject({
error: "Process command failed",
category: "invalid_input",
message:
"Capture evidence is malformed. Create new capture evidence, then try again.",
code: "invalid_request",
details: {
operation: "compare_process_captures",
issues: expect.arrayContaining([
expect.objectContaining({
path: ["parameters"],
reason: "missing_argument",
}),
]),
},
});
});
});
it(
"reports the JSON depth constraint through the compiled comparison CLI",
async () => {
const root = await fixture();
const left = join(root, "left.json");
const right = join(root, "right.json");
const input = INVESTIGATION_EXAMPLES.compare_process_captures.input;
await writeFile(left, JSON.stringify(input.left));
await writeFile(right, JSON.stringify(input.right));
const cli = fileURLToPath(
new URL("../../../scripts/rea.mjs", import.meta.url),
);
const cliArguments = [
cli,
"compare-process-captures",
left,
right,
"--json",
];
const baseline = await execFileAsync(process.execPath, cliArguments);
expect(JSON.parse(baseline.stdout)).toMatchObject({
operation: "compare_process_captures",
});
const nested = '{"nested":'.repeat(10_000) + "1" + "}".repeat(10_000);
await writeFile(
left,
JSON.stringify({
...input.left,
parameters: { attack: "depth-placeholder" },
}).replace('"depth-placeholder"', nested),
);
await expect(
execFileAsync(process.execPath, cliArguments),
).rejects.toMatchObject({
code: 1,
stdout: expect.stringContaining("maximum nesting depth"),
});
},
CLI_INTEGRATION_TIMEOUT_MS,
);
+36
View File
@@ -21,6 +21,8 @@ import { createServer } from "../../../src/server/createServer.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { silentLogger } from "../../../src/logger.js";
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
import { MAX_JSON_DEPTH } from "../../../src/domain/jsonValue.js";
import { INVESTIGATION_EXAMPLES } from "../../../src/contracts/investigationExamples.js";
import { ok as resultOk } from "../../../src/domain/result.js";
import {
createSessionMcpHarness,
@@ -260,6 +262,40 @@ describe("session filesystem path boundaries over MCP", () => {
}, 10_000);
});
describe("json depth bounds over MCP", () => {
it("classifies deeply nested evidence parameters as an input validation error", async () => {
directory = await createTestTempDirectory("rea-mcp-depth-bound-");
const { mcp } = await createSessionMcpHarness(
directory,
provider,
resources,
);
const input = INVESTIGATION_EXAMPLES.compare_process_captures.input;
const baseline = await mcp.callTool({
name: "compare_process_captures",
arguments: input,
});
expect(baseline.isError, JSON.stringify(baseline)).not.toBe(true);
let value: unknown = 1;
for (let index = 0; index <= MAX_JSON_DEPTH; index += 1)
value = { nested: value };
const result = await mcp.callTool({
name: "compare_process_captures",
arguments: {
left: {
...input.left,
parameters: { attack: value },
},
right: input.right,
},
});
const text = JSON.stringify(result.content);
expect(result.isError, text).toBe(true);
expect(text).toContain("Input validation");
expect(text).toContain("maximum nesting depth");
}, 10_000);
});
describe("process residuals over MCP", () => {
it("records process residuals linked to capture Evidence", async () => {
if (!(await probeProcessCaptureCapability()).available) return;