mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(pcap): retain producer contradiction diagnostics (#7937)
* fix(pcap): retain producer contradiction diagnostics * test(pcap): align DNS fixture with empty captures
This commit is contained in:
1 parent
c92322b473
commit
0386efd1e1
4 files changed
+118
-1
No files matched your search
@@ -1007,6 +1007,13 @@ export class PcapProvider {
|
||||
if (metadata.sha256 !== null && metadata.sha256 !== identity.sha256)
|
||||
throw new AnalysisProtocolError(
|
||||
"capinfos SHA256 disagrees with the owned capture snapshot",
|
||||
{
|
||||
diagnostics: {
|
||||
reason: "capture_sha256_mismatch",
|
||||
capinfos_sha256: metadata.sha256,
|
||||
snapshot_sha256: identity.sha256,
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (cause) {
|
||||
throw withOutputCleanup(cause, info);
|
||||
@@ -1119,6 +1126,13 @@ export class PcapProvider {
|
||||
if (fields.packetCount !== metadata.packetCount)
|
||||
throw new AnalysisProtocolError(
|
||||
`capinfos counted ${String(metadata.packetCount)} packets but tshark returned ${String(fields.packetCount)}`,
|
||||
{
|
||||
diagnostics: {
|
||||
reason: "packet_count_mismatch",
|
||||
capinfos_packet_count: metadata.packetCount,
|
||||
tshark_packet_count: fields.packetCount,
|
||||
},
|
||||
},
|
||||
);
|
||||
const captured = {
|
||||
result: {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect } from "vitest";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { toolContract } from "../../src/contracts/toolContracts.js";
|
||||
import { parseEvidence } from "../../src/domain/evidence.js";
|
||||
@@ -47,6 +49,46 @@ const environmentFor = (
|
||||
REA_CAPINFOS_COMMAND: tools.capinfos,
|
||||
});
|
||||
|
||||
cliTest.skipIf(process.platform !== "linux")(
|
||||
"preserves contradictory packet counts through CLI and MCP",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-pcap-count-mismatch-");
|
||||
const path = join(root, "capture.pcap");
|
||||
await writeFile(path, Buffer.from("pcap-fixture-bytes"));
|
||||
const { tshark, capinfos } = await writeStubTools(root, {
|
||||
tsharkJson: "[]",
|
||||
capinfosText:
|
||||
"File type: pcap\nFile encapsulation: Ethernet\nNumber of packets: 1\n",
|
||||
});
|
||||
const environment = environmentFor(root, { tshark, capinfos });
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-pcap-capture", path, "--json"],
|
||||
environment,
|
||||
});
|
||||
expect(response.exitCode, response.stdout).toBe(1);
|
||||
expect(response.json).toMatchObject({
|
||||
code: "unreadable_output",
|
||||
details: {
|
||||
diagnostics: {
|
||||
reason: "packet_count_mismatch",
|
||||
capinfos_packet_count: 1,
|
||||
tshark_packet_count: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { client, call } = await connectLocalToolsMcp(environment);
|
||||
try {
|
||||
const reply = await call("inspect_pcap_capture", { path });
|
||||
expect(reply.isError).toBe(true);
|
||||
const text = reply.content.find((item) => item.type === "text");
|
||||
if (text?.type !== "text") throw new Error("Missing MCP error text");
|
||||
expect(JSON.parse(text.text)).toEqual({ error: response.json });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"returns the same unreadable-output diagnostic for invalid version streams through CLI and stdio MCP",
|
||||
async ({ cli }) => {
|
||||
|
||||
@@ -158,6 +158,66 @@ it("inspects a capture with endpoints, DNS, TLS SNI, and HTTP", async () => {
|
||||
expect(result.coverage).toBe("complete");
|
||||
});
|
||||
|
||||
it.each([0, 1])(
|
||||
"preserves packet-count mismatch facts while accepting an empty capture: capinfos=%s",
|
||||
async (count) => {
|
||||
const { provider, root } = await providerWith({
|
||||
tsharkJson: "[]",
|
||||
dnsJson: "[]",
|
||||
capinfosText: `File type: pcap\nFile encapsulation: Ethernet\nNumber of packets: ${String(count)}\n`,
|
||||
});
|
||||
const path = await captureFixture(root);
|
||||
const result = await provider.execute({
|
||||
operation: "inspect_pcap_capture",
|
||||
input: { path },
|
||||
});
|
||||
if (count === 0) {
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.result).toMatchObject({
|
||||
packet_count: 0,
|
||||
coverage: "complete",
|
||||
});
|
||||
} else {
|
||||
if (result.ok) throw new Error("Expected contradictory counts");
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "unreadable_output",
|
||||
details: {
|
||||
diagnostics: {
|
||||
reason: "packet_count_mismatch",
|
||||
capinfos_packet_count: 1,
|
||||
tshark_packet_count: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("retains both observed digests when capinfos disagrees with the snapshot", async () => {
|
||||
const reported = "0".repeat(64);
|
||||
const { provider, root } = await providerWith({
|
||||
capinfosText: `File type: pcap\nFile encapsulation: Ethernet\nNumber of packets: 0\nSHA256: ${reported}\n`,
|
||||
});
|
||||
const path = await captureFixture(root);
|
||||
const result = await provider.execute({
|
||||
operation: "inspect_pcap_capture",
|
||||
input: { path },
|
||||
});
|
||||
if (result.ok) throw new Error("Expected contradictory snapshot digest");
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "unreadable_output",
|
||||
details: {
|
||||
diagnostics: {
|
||||
reason: "capture_sha256_mismatch",
|
||||
capinfos_sha256: reported,
|
||||
snapshot_sha256: createHash("sha256")
|
||||
.update("pcap-fixture-bytes")
|
||||
.digest("hex"),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("retains an absolute HTTP request target without a Host-derived full URI", async () => {
|
||||
const { provider, root } = await providerWith({ httpAbsoluteUriOnly: true });
|
||||
const path = await captureFixture(root);
|
||||
|
||||
Vendored
+2
-1
@@ -93,6 +93,7 @@ export const TSHARK_DNS_JSON = JSON.stringify([
|
||||
export interface PcapStubToolsOptions {
|
||||
readonly failCapinfos?: boolean;
|
||||
readonly malformedDnsJson?: boolean;
|
||||
readonly dnsJson?: string;
|
||||
readonly tsharkJson?: string;
|
||||
readonly capinfosText?: string;
|
||||
readonly httpAbsoluteUriOnly?: boolean;
|
||||
@@ -142,7 +143,7 @@ case " $* " in
|
||||
case " $* " in *" --no-duplicate-keys "*) ;; *) exit 2 ;; esac
|
||||
case " $* " in *" -J frame dns mdns llmnr "*) ;; *) exit 2 ;; esac
|
||||
case " $* " in *" -Y dns || mdns || llmnr "*) ;; *) exit 2 ;; esac
|
||||
printf '%s' '${(options.malformedDnsJson ? "{" : TSHARK_DNS_JSON).replace(/'/gu, "'\\''")}'
|
||||
printf '%s' '${(options.malformedDnsJson ? "{" : (options.dnsJson ?? TSHARK_DNS_JSON)).replace(/'/gu, "'\\''")}'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
|
||||
Reference in new issue
Block a user