From c76e981dcdcdd8e6c146c3ec044361c3471efc1e Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:24:12 +0000 Subject: [PATCH] fix(artifacts): stop offering integrity-policy retries for checksum-invalid DMGs integrity_policy=record-and-continue only records declared-versus-observed digest contradictions, which carry declared and observed facts. Integrity failures without those facts, such as hdiutil checksum verification, fail closed under either policy, so the policy retry advice could never succeed. Explain the limitation and recommend a valid copy instead. Fixes #5549 Co-Authored-By: Claude Opus 5.5 --- src/domain/analysisErrorPresentation.ts | 10 ++++ tests/boundary/mcp/artifactMcp.test.ts | 80 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/domain/analysisErrorPresentation.ts b/src/domain/analysisErrorPresentation.ts index 57cc940ea7..2967fffebd 100644 --- a/src/domain/analysisErrorPresentation.ts +++ b/src/domain/analysisErrorPresentation.ts @@ -447,6 +447,16 @@ const artifactMessage = ({ return `ASAR unpacked companion bytes are unavailable for ${details.logicalPath}. Select the archive in place with its .unpacked directory beside it, then retry.`; if (reason === "unavailable") return "Artifact processing is unavailable for the current target or host. Check artifact support and required tools."; + // integrity_policy records only declared-versus-observed digest + // contradictions, which carry their declared and observed facts. Container + // self-verification (such as a DMG checksum) and source changes fail closed + // under either policy, so offering a policy retry would be ineffective. + if ( + reason === "integrity" && + details === undefined && + INTEGRITY_POLICY_OPERATIONS.has(operation) + ) + return "Artifact failed an integrity check that integrity_policy cannot relax, such as a container checksum or a source that changed while being read. Get a valid, unchanged copy and try again."; if (reason === "integrity" && INTEGRITY_POLICY_OPERATIONS.has(operation)) return `${ details?.unpacked === true && diff --git a/tests/boundary/mcp/artifactMcp.test.ts b/tests/boundary/mcp/artifactMcp.test.ts index d84c139100..0e5023127d 100644 --- a/tests/boundary/mcp/artifactMcp.test.ts +++ b/tests/boundary/mcp/artifactMcp.test.ts @@ -22,6 +22,7 @@ import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { ArtifactProvider } from "../../../src/artifacts/ArtifactProvider.js"; import { ARTIFACT_COMPARISON_EXAMPLE } from "../../../src/contracts/artifactComparisonExample.js"; import { createServer } from "../../../src/server/createServer.js"; +import { execFileOutput } from "../../../src/process/ExecFileOutput.js"; import { observed } from "../../fixtures/analysisExecution.js"; const integrityMessage = (unpacked: boolean): string => @@ -104,6 +105,85 @@ it.each([ 20_000, ); +it.skipIf(process.platform !== "darwin")( + "does not offer an ineffective integrity-policy retry for a checksum-invalid DMG", + async () => { + const root = await createTestTempDirectory("rea-dmg-checksum-mcp-"); + const writableImagePath = join(root, "writable.dmg"); + const imagePath = join(root, "fixture.dmg"); + await execFileOutput("/usr/bin/hdiutil", [ + "create", + "-quiet", + "-size", + "1m", + "-layout", + "NONE", + "-type", + "UDIF", + writableImagePath, + ]); + await execFileOutput("/usr/bin/hdiutil", [ + "convert", + "-quiet", + writableImagePath, + "-format", + "UDRO", + "-o", + imagePath, + ]); + const image = await readFile(imagePath); + const footer = image.subarray(-512); + expect(footer.subarray(0, 4).toString("ascii")).toBe("koly"); + const corruptionOffset = Number(footer.readBigUInt64BE(24)) + 4096; + const originalByte = image[corruptionOffset]; + if (originalByte === undefined) + throw new Error("expected a byte in the generated image data fork"); + image[corruptionOffset] = originalByte ^ 0x01; + await writeFile(imagePath, image); + + const session = createTestBinarySession(new ArtifactProvider(process.env)); + const server = createServer({ kind: "session", session }); + const client = new Client({ name: "dmg-integrity-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: imagePath }, + }); + expect(opened.isError).not.toBe(true); + + const message = + "Artifact failed an integrity check that integrity_policy cannot relax, such as a container checksum or a source that changed while being read. Get a valid, unchanged copy and try again."; + for (const integrity_policy of ["fail", "record-and-continue"]) { + const result = await client.callTool({ + name: "inspect_artifact", + arguments: { integrity_policy }, + }); + expect(result.isError).toBe(true); + expect(parseMcpToolError(result)).toMatchObject({ + error: { + code: "artifact_operation_failed", + category: "integrity_mismatch", + message, + retryable: false, + remediation: { action: message }, + }, + }); + } + } finally { + await Promise.allSettled([ + client.close(), + server.close(), + session.close(), + ]); + } + }, + 60_000, +); + it("extracts an active archive through MCP when requested", async () => { const root = await createTestTempDirectory("rea-artifact-extract-mcp-"); const archive = join(root, "fixture.zip");