import { Client } from "@modelcontextprotocol/client"; import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; import { expect } from "vitest"; import { toolContract } from "../../../src/contracts/toolContracts.js"; import { peResourcesSchema } from "../../../src/domain/native/peResources.js"; import { peResourceFixture } from "../../../src/native/pe/PeResources.fixture.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { cliTest, runFileCli, type TestCli, } from "../../support/cli/cliFixture.js"; type PeResourceCall = ( arguments_: Record, ) => ReturnType; const expectPeInputFailureAcrossAdapters = async (input: { readonly cli: TestCli; readonly call: PeResourceCall; readonly path: string; readonly reason: "invalid_format" | "invalid_value"; }) => { const { cli, call, path, reason } = input; const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode).toBe(1); expect(result.json).toMatchObject({ code: "invalid_request", category: "invalid_input", details: { operation: "inspect_pe_resources", issues: [{ path: ["path"], reason }], }, }); const response = await call({ path }); expect(response.isError).toBe(true); expect(parseMcpToolError(response)).toEqual({ error: result.json }); }; const expectPePaddingFailureAcrossAdapters = async (input: { readonly cli: TestCli; readonly call: PeResourceCall; readonly path: string; }) => { const { cli, call, path } = input; const fixture = peResourceFixture(); const virtualSize = fixture.bytes.readUInt32LE(fixture.sectionAt + 8); fixture.bytes.writeUInt32LE( 0x2000 + virtualSize, fixture.dataOffsets[0] ?? 0, ); fixture.bytes.writeUInt32LE(4, (fixture.dataOffsets[0] ?? 0) + 4); await writeFile(path, fixture.bytes); const cliResult = await cli.run({ arguments: ["inspect-pe-resources", path, "--json"], }); expect(cliResult.exitCode).not.toBe(0); const mcpResult = await call({ path }); expect(mcpResult.isError).toBe(true); expect(peResourcesSchema.safeParse(mcpResult.structuredContent).success).toBe( false, ); }; const expectPeInputFailuresAcrossAdapters = async (input: { readonly cli: TestCli; readonly call: PeResourceCall; readonly root: string; }) => { const { cli, call, root } = input; const malformedPath = join(root, "malformed.exe"); await writeFile(malformedPath, Buffer.from("not a PE image")); for (const [path, reason] of [ [join(root, "missing.exe"), "invalid_value"], [malformedPath, "invalid_format"], ] as const) await expectPeInputFailureAcrossAdapters({ cli, call, path, reason, }); }; const contract = toolContract("inspect_pe_resources"); const expectPeWordAlignedRecordsAcrossAdapters = async (input: { readonly cli: TestCli; readonly call: PeResourceCall; readonly root: string; }): Promise => { const { cli, call, root } = input; for (const plus of [false, true]) { for (const [index, identities] of [ { type: 10, name: 1, language: "AB" }, { type: "AB", name: 1, language: 1033 }, ].entries()) { const fixture = peResourceFixture( plus, [{ ...identities, payload: Buffer.from("opaque resource") }], 65536, 2, ); const path = join(root, `word-aligned-${plus}-${index}.exe`); await writeFile(path, fixture.bytes); const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); const evidence = contract.outputSchema.parse(result.json); const report = peResourcesSchema.parse(evidence.normalized_result); expect(report.coverage).toMatchObject({ status: "complete", resources: 1, }); expect(report.resources[0]?.data_entry_location).toEqual({ offset: fixture.dataOffsets[0], bytes: 16, }); expect(report.resources[0]?.payload.sha256).toBe( createHash("sha256").update("opaque resource").digest("hex"), ); const response = await call({ path }); expect(response.isError).not.toBe(true); expect( contract.outputSchema.parse(response.structuredContent) .normalized_result, ).toEqual(evidence.normalized_result); expect(await readFile(path)).toEqual(fixture.bytes); } } }; const expectPeMappingOwnershipAcrossAdapters = async ( cli: TestCli, call: PeResourceCall, root: string, ): Promise => { for (const { name, payloadRva, payloadBytes, section, expectedOffset } of [ { name: "empty-header", payloadRva: 0x300, payloadBytes: 0, section: null, expectedOffset: 0x300, }, { name: "header-section-collision", payloadRva: 0x300, payloadBytes: 0, section: { rva: 0x300, bytes: 4 }, expectedOffset: null, }, { name: "empty-section-in-header", payloadRva: 0x300, payloadBytes: 4, section: { rva: 0x301, bytes: 0 }, expectedOffset: 0x300, }, { name: "empty-section-in-resources", payloadRva: null, payloadBytes: 4, section: { rva: 0x2001, bytes: 0 }, expectedOffset: "original", }, ]) { const fixture = peResourceFixture(false, [ { type: 10, name: 1, language: 1033, payload: Buffer.from("DATA") }, ]); const data = fixture.dataOffsets[0] ?? 0; let bytes = fixture.bytes; const originalPayloadOffset = bytes.readUInt32LE(data) - 0x1c00; if (payloadRva !== null) bytes.writeUInt32LE(payloadRva, data); bytes.writeUInt32LE(payloadBytes, data + 4); if (section !== null) { bytes.writeUInt16LE(2, 0x86); const at = fixture.sectionAt + 40; bytes.writeUInt32LE(section.bytes, at + 8); bytes.writeUInt32LE(section.rva, at + 12); bytes.writeUInt32LE(section.bytes, at + 16); bytes.writeUInt32LE(bytes.length, at + 20); bytes = Buffer.concat([bytes, Buffer.alloc(section.bytes)]); } const path = join(root, `${name}.exe`); await writeFile(path, bytes); if (expectedOffset === null) { await expectPeInputFailureAcrossAdapters({ cli, call, path, reason: "invalid_format", }); continue; } const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode, name).toBe(0); const evidence = contract.outputSchema.parse(result.json); const response = await call({ path }); expect(response.isError, name).not.toBe(true); const mcp = contract.outputSchema.parse(response.structuredContent); expect(mcp.subject).toEqual(evidence.subject); expect(mcp.evidence_id).toBe(evidence.evidence_id); expect(mcp.normalized_result).toEqual(evidence.normalized_result); expect(evidence.normalized_result).toMatchObject({ coverage: { status: "complete", resources: 1 }, resources: [ { payload: { location: { offset: expectedOffset === "original" ? originalPayloadOffset : expectedOffset, bytes: payloadBytes, }, }, }, ], }); expect(await readFile(path)).toEqual(bytes); } }; const expectPeInventoriesAcrossAdapters = async ( cli: TestCli, call: PeResourceCall, root: string, ): Promise => { for (const [plus, empty] of [ [false, false], [true, false], [false, true], [true, true], ]) { const fixture = peResourceFixture(plus); const virtualSize = fixture.bytes.readUInt32LE(fixture.sectionAt + 8); if (empty) { fixture.bytes.writeUInt32LE(0x2000 + virtualSize, fixture.dataOffsets[0]); fixture.bytes.writeUInt32LE(0, (fixture.dataOffsets[0] ?? 0) + 4); } const path = join( root, `resource${plus ? "64" : "32"}${empty ? "-empty" : ""}.exe`, ); await writeFile(path, fixture.bytes); const invocation = { arguments: ["inspect-pe-resources", path, "--json"], }; // Keep an independent public-wrapper process for both PE32 and PE32+. // The empty-payload variants reuse only compiled-command startup. const result = await (empty ? runFileCli(cli, invocation) : cli.run(invocation)); expect(result.exitCode).toBe(0); const evidence = contract.outputSchema.parse(result.json); const report = peResourcesSchema.parse(evidence.normalized_result); expect(evidence.subject?.digest).toMatchObject({ sha256: createHash("sha256").update(fixture.bytes).digest("hex"), }); expect(evidence.subject?.architecture).toBe(plus ? "x86_64" : "x86"); expect(report.coverage).toMatchObject({ status: "complete", resources: 5, }); expect(report.resources[0]?.payload.sha256).toBe( createHash("sha256") .update(empty ? "" : "ICON") .digest("hex"), ); if (empty) expect(report.resources[0]?.payload.location).toEqual({ offset: 0x400 + virtualSize, bytes: 0, }); expect( report.resources.find(({ name }) => name.kind === "name")?.name, ).toMatchObject({ kind: "name", name: "7", utf16le_hex: "3700" }); expect(report.icon_groups[0]?.images).toMatchObject([ { resource_id: 1, candidate_resource_indices: [0, 1], same_language_resource_index: 0, }, { resource_id: 99, candidate_resource_indices: [], same_language_resource_index: null, }, ]); const response = await call({ path }); expect(response.isError).not.toBe(true); const mcp = contract.outputSchema.parse(response.structuredContent); expect(mcp.subject).toEqual(evidence.subject); expect(mcp.evidence_id).toBe(evidence.evidence_id); expect(mcp.normalized_result).toEqual(evidence.normalized_result); const text = response.content.find(({ type }) => type === "text"); if (text?.type !== "text") throw new Error("Missing MCP text Evidence"); expect(contract.outputSchema.parse(JSON.parse(text.text)).evidence_id).toBe( evidence.evidence_id, ); expect(await readFile(path)).toEqual(fixture.bytes); } }; cliTest( "inspects PE32/PE32+ resources through compiled CLI and real stdio MCP without an active target", async ({ cli }) => { const root = await createTestTempDirectory("rea-pe-resource-acceptance-"); const transport = new StdioClientTransport({ command: process.execPath, args: [resolve("scripts/rea.mjs"), "mcp"], cwd: process.cwd(), env: { PATH: process.env.PATH ?? "", REA_LOG_LEVEL: "silent" }, stderr: "pipe", }); const client = new Client({ name: "pe-resource-acceptance", version: "1" }); try { await client.connect(transport); const inventory = await client.listTools(); const definition = inventory.tools.find( ({ name }) => name === contract.name, ); if (definition === undefined) throw new Error("Missing PE resource tool contract"); expect(inventory.tools).toContainEqual( expect.objectContaining({ name: contract.name, annotations: expect.objectContaining({ readOnlyHint: false, openWorldHint: false, }), }), ); const call: PeResourceCall = (arguments_) => client.callTool( { name: definition.name, arguments: arguments_ }, { toolDefinition: definition }, ); const sessionDefinition = inventory.tools.find( ({ name }) => name === "binary_session", ); if (sessionDefinition === undefined) throw new Error("Missing binary_session tool contract"); const session = await client.callTool( { name: sessionDefinition.name, arguments: {} }, { toolDefinition: sessionDefinition }, ); expect(JSON.stringify(session.structuredContent)).toContain( "inspect_pe_resources", ); await expectPeInventoriesAcrossAdapters(cli, call, root); await expectPeWordAlignedRecordsAcrossAdapters({ cli, call, root, }); await expectPeMappingOwnershipAcrossAdapters(cli, call, root); await expectPePaddingFailureAcrossAdapters({ cli, call, path: join(root, "raw-padding.exe"), }); await expectPeInputFailuresAcrossAdapters({ cli, call, root, }); const absent = peResourceFixture(); absent.bytes.fill(0, absent.directoryAt, absent.directoryAt + 8); const path = join(root, "absent.exe"); await writeFile(path, absent.bytes); const empty = await call({ path }); expect(empty.isError).not.toBe(true); expect( contract.outputSchema.parse(empty.structuredContent).normalized_result, ).toMatchObject({ directory: null, resources: [], coverage: { status: "complete", resources: 0 }, }); for (const arguments_ of [ { path, max_file_bytes: 64 }, { path: "relative.exe" }, { path, extra: true }, ]) { const rejected = await call(arguments_); expect(rejected.isError).toBe(true); expect( contract.outputSchema.safeParse(rejected.structuredContent).success, ).toBe(false); } const damaged = peResourceFixture(); damaged.bytes.writeUInt32LE(17, damaged.directoryAt + 4); const badPath = join(root, "damaged.exe"); await writeFile(badPath, damaged.bytes); const rejected = await call({ path: badPath }); expect(rejected.isError).toBe(true); expect( contract.outputSchema.safeParse(rejected.structuredContent).success, ).toBe(false); await client.ping(); } finally { try { await client.close(); } finally { await transport.close(); } } }, 30_000, );