Files
rea/tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
morluto b38429cb6a fix(native): preserve universal Mach-O container identity
Keep FAT container format distinct from selected slice architecture through target admission, Evidence, snapshots, and CLI/MCP lifecycle results. Preserve native, Hopper, Ghidra, extraction, and dylib routing for the Mach-O family.

Reject selected FAT ranges overlapping their table or too short for a Mach-O header. Replace empty-slice fixtures with in-bounds executable headers and cover thin, universal, single-slice, FAT64, and swapped containers.

Validation: static and architecture checks, generated docs, targeted producer and public MCP tests. Provider routing uses controlled fixtures; no live Hopper, IDA, JADX, or Ghidra analysis was performed for this change.
2026-10-12 09:29:07 +08:00

82 lines
3.1 KiB
TypeScript

import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest";
import { NativeMacOSProvider } from "../../../src/native/NativeMacOSProvider.js";
import {
machoImage,
rpathCommand,
CPU,
fatImage,
} from "../../../src/artifacts/apple/MachoImage.fixture.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { NativeFixtureRunner } from "../../fixtures/nativeCommands.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
it.each(["mach-o", "mach-o-universal"] as const)(
"rejects changed %s signature targets through MCP and accepts reopening the new version",
async (format) => {
const path = join(
await createTestTempDirectory("rea-signature-binding-mcp-"),
"program",
);
const image = (commands: readonly Uint8Array[] = []) =>
format === "mach-o"
? machoImage({ commands })
: fatImage(
[CPU.arm64, CPU.x86_64].map((cpu) => ({
cpu,
bytes: machoImage({ cpu, commands }),
})),
);
await writeFile(path, image());
const session = createTestBinarySession(
new NativeMacOSProvider({}, new NativeFixtureRunner(), "darwin"),
);
const server = createServer({ kind: "session", session });
const client = new Client({ name: "signature-binding-test", version: "1" });
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
try {
await server.connect(serverTransport);
await client.connect(clientTransport);
const open = () =>
client.callTool({ name: "open_binary", arguments: { path } });
const opened = await open();
expect(opened.isError).not.toBe(true);
expect(
toolContract("open_binary").outputSchema.parse(opened.structuredContent)
.result,
).toMatchObject({ format });
await writeFile(path, image([rpathCommand("new-version")]));
const changed = await client.callTool({
name: "inspect_signature",
arguments: {},
});
expect(changed.isError).toBe(true);
expect(parseMcpToolError(changed)).toMatchObject({
error: { code: "artifact_changed", details: { path } },
});
expect((await open()).isError).not.toBe(true);
const inspected = await client.callTool({
name: "inspect_signature",
arguments: {},
});
expect(
inspected.isError,
JSON.stringify(inspected.structuredContent),
).not.toBe(true);
expect(inspected.structuredContent).toMatchObject({
subject: { format },
normalized_result: { signed: true, identifier: "com.example.fixture" },
});
} finally {
await client.close();
await server.close();
}
},
);