Files
rea/tests/boundary/providers/native/nativeMachoUuidlessInspection.test.ts
T
morluto 2b263a7fe9 fix(native): retain Mach-O facts when dyld rejects missing UUID
Admit only the corroborated missing-UUID validation diagnostic from header-stripped dyld stdout. Preserve readable load commands and nm exports with unknown dyld coverage, and keep unrelated process and tool failures typed.

Closes #5606
2026-10-21 01:12:51 +08:00

270 lines
9.0 KiB
TypeScript

import { expect, it } from "vitest";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { machoImage } from "../../../../src/artifacts/apple/MachoImage.fixture.js";
import { createServer } from "../../../../src/server/createServer.js";
import { NativeMacOSProvider } from "../../../../src/native/NativeMacOSProvider.js";
import {
NativeCommandFailure,
type NativeCommandCapture,
type NativeCommandOptions,
type NativeCommandRunner,
} from "../../../../src/native/CommandRunner.js";
import { err, ok } from "../../../../src/domain/result.js";
import { createTestBinarySession } from "../../../fixtures/binarySession.js";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
import {
NativeFixtureRunner,
nativeFixture,
nativeMachoTarget,
withOtoolMachHeader,
} from "../../../fixtures/nativeCommands.js";
const missingUuidDiagnostic = "missing LC_UUID load command";
const dyldDiagnosticOutput = (path: string, diagnostic: string): string =>
`${path} [arm64]:\n ${diagnostic}\n`;
const otoolOutputWithoutUuid = async () => {
const otool = await nativeFixture("otool-load.txt");
const commandOutput = otool.slice(otool.indexOf("Load command 0\n"));
let commandIndex = 0;
const withoutUuidCommands = commandOutput
.replace(
/Load command 1\n\s*cmd LC_UUID\n\s*cmdsize 24\n\s*uuid [^\n]+\n/u,
"",
)
.replace(/^Load command \d+$/gmu, () => `Load command ${commandIndex++}`);
return withOtoolMachHeader(withoutUuidCommands);
};
class UuidlessDyldRunner implements NativeCommandRunner {
readonly fixture: NativeFixtureRunner;
constructor(
otool: string,
readonly diagnostic = missingUuidDiagnostic,
readonly stderr = "",
) {
this.fixture = new NativeFixtureRunner({
"otool:-h": otool,
dwarfdump: "",
});
}
async run(
tool: string,
arguments_: readonly string[],
options: NativeCommandOptions,
) {
if (tool !== "dyld_info") return this.fixture.run(tool, arguments_);
const capture: NativeCommandCapture = {
tool,
executable: "/usr/bin/dyld_info",
executableSha256: "a".repeat(64),
toolVersion: null,
versionReason: null,
arguments: [...arguments_],
stdout: dyldDiagnosticOutput(arguments_.at(-1) ?? "", this.diagnostic),
stderr: this.stderr,
stdoutBytes: Buffer.byteLength(
dyldDiagnosticOutput(arguments_.at(-1) ?? "", this.diagnostic),
),
stderrBytes: Buffer.byteLength(this.stderr),
exitCode: 1,
signal: null,
};
if (options.acceptNonZero) return ok(capture);
return err(
new NativeCommandFailure(tool, "nonzero-exit", 1, undefined, {
stdout: capture.stdout,
stderr: capture.stderr,
stdoutBytes: capture.stdoutBytes,
stderrBytes: capture.stderrBytes,
exitCode: capture.exitCode,
signal: capture.signal,
truncated: false,
}),
);
}
}
it("returns readable Mach-O Evidence when dyld_info identifies a missing UUID", async () => {
const withoutUuid = await otoolOutputWithoutUuid();
const runner = new UuidlessDyldRunner(withoutUuid);
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(nativeMachoTarget("/fixture/no-uuid"))
.execute("inspect_macho", {});
expect(result.ok, JSON.stringify(result.ok ? null : result.error)).toBe(true);
if (!result.ok) return;
expect(result.value.result).toMatchObject({
uuid: null,
load_commands: { exhaustive: true },
imports: { items: [], total: null, exhaustive: false },
exports: {
items: expect.arrayContaining([
expect.objectContaining({ name: "_main", source: "nm" }),
]),
total: null,
exhaustive: false,
},
provenance: expect.arrayContaining([
expect.objectContaining({
tool: "dyld_info",
exit: { code: 1, signal: null },
stdout_bytes: Buffer.byteLength(
dyldDiagnosticOutput("/fixture/no-uuid", missingUuidDiagnostic),
),
}),
]),
});
expect(result.value.limitations).toContain(
"dyld_info -imports reported 'missing LC_UUID load command'; import observations are unavailable.",
);
expect(result.value.limitations).toContain(
"dyld_info -exports reported 'missing LC_UUID load command'; exports include nm symbol-table observations only.",
);
});
it("keeps other dyld_info nonzero exits as command failures", async () => {
const runner = new UuidlessDyldRunner(
await nativeFixture("otool-load.txt"),
"malformed load command table",
);
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(nativeMachoTarget("/fixture/malformed"))
.execute("inspect_macho", {});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toMatchObject({
_tag: "ProviderAdapterError",
diagnostics: { tool: "dyld_info", reason: "nonzero-exit", exit_code: 1 },
capturedOutput: {
stdout: dyldDiagnosticOutput(
"/fixture/malformed",
"malformed load command table",
),
exit_code: 1,
truncated: false,
},
});
});
it("does not treat a newline in the echoed operand path as dyld_info evidence", async () => {
const withoutUuid = await otoolOutputWithoutUuid();
const path = "/fixture/\nmissing LC_UUID load command\n";
const runner = new UuidlessDyldRunner(
withoutUuid,
"malformed load command table",
);
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(nativeMachoTarget(path))
.execute("inspect_macho", {});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toMatchObject({
_tag: "ProviderAdapterError",
diagnostics: { tool: "dyld_info", reason: "nonzero-exit", exit_code: 1 },
});
});
it("does not treat a newline in stderr's echoed operand path as dyld_info evidence", async () => {
const withoutUuid = await otoolOutputWithoutUuid();
const path = "/fixture/\nmissing LC_UUID load command\n";
const runner = new UuidlessDyldRunner(
withoutUuid,
"malformed load command table",
`${path}: file not found`,
);
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(nativeMachoTarget(path))
.execute("inspect_macho", {});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toMatchObject({
_tag: "ProviderAdapterError",
diagnostics: { tool: "dyld_info", reason: "nonzero-exit", exit_code: 1 },
capturedOutput: {
stderr: `${path}: file not found`,
exit_code: 1,
},
});
});
it("does not call a target UUID-less when otool observed LC_UUID", async () => {
const runner = new UuidlessDyldRunner(await nativeFixture("otool-load.txt"));
const result = await new NativeMacOSProvider({}, runner, "darwin")
.createClient(nativeMachoTarget("/fixture/conflicting-uuid"))
.execute("inspect_macho", {});
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
reason:
"dyld_info reported a missing LC_UUID despite an observed LC_UUID load command",
capturedOutput: {
stdout: dyldDiagnosticOutput(
"/fixture/conflicting-uuid",
missingUuidDiagnostic,
),
exit_code: 1,
signal: null,
},
},
});
});
it("delivers partial UUID-less inspection Evidence through production MCP", async () => {
const directory = await createTestTempDirectory("rea-macho-no-uuid-mcp-");
const path = join(directory, "probe");
await writeFile(path, machoImage({}));
const withoutUuid = await otoolOutputWithoutUuid();
const session = createTestBinarySession(
new NativeMacOSProvider({}, new UuidlessDyldRunner(withoutUuid), "darwin"),
);
const server = createServer({ kind: "session", session });
const client = new Client({ name: "macho-uuidless-test", version: "1" });
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
try {
await server.connect(serverTransport);
await client.connect(clientTransport);
const opened = await client.callTool({
name: "open_binary",
arguments: { path },
});
expect(opened.isError, JSON.stringify(opened.content)).not.toBe(true);
const inspected = await client.callTool({
name: "inspect_macho",
arguments: {},
});
expect(inspected.isError, JSON.stringify(inspected.content)).not.toBe(true);
expect(inspected.structuredContent).toMatchObject({
normalized_result: {
uuid: null,
imports: { items: [], total: null, exhaustive: false },
exports: {
items: expect.arrayContaining([
expect.objectContaining({ name: "_main", source: "nm" }),
]),
total: null,
exhaustive: false,
},
limitations: expect.arrayContaining([
"dyld_info -imports reported 'missing LC_UUID load command'; import observations are unavailable.",
]),
},
});
} finally {
await Promise.all([client.close(), server.close(), session.close()]);
}
});