mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
267 lines
8.1 KiB
TypeScript
267 lines
8.1 KiB
TypeScript
import { create, toBinary } from "@bufbuild/protobuf";
|
|
import {
|
|
FieldDescriptorProtoSchema,
|
|
FileDescriptorSetSchema,
|
|
OneofDescriptorProtoSchema,
|
|
} from "@bufbuild/protobuf/wkt";
|
|
import { createHash } from "node:crypto";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { webNetworkCaptureSchema } from "../../../src/domain/webNetworkCapture.js";
|
|
import { protobufHar } from "../../fixtures/protobufCapture.js";
|
|
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const sha256 = (bytes: string | Uint8Array) =>
|
|
createHash("sha256").update(bytes).digest("hex");
|
|
// Two occurrences of first=1, second=3, then child { value=4 }.
|
|
const body = Buffer.from("0801080210031a020804", "hex");
|
|
const descriptorDefinition = () =>
|
|
create(FileDescriptorSetSchema, {
|
|
file: [
|
|
{
|
|
name: "identity.proto",
|
|
package: "capture",
|
|
syntax: "proto3",
|
|
messageType: [
|
|
{
|
|
name: "Message",
|
|
field: [
|
|
{ name: "first", number: 1, type: 5, label: 3 },
|
|
{ name: "second", number: 2, type: 5, label: 1 },
|
|
{
|
|
name: "child",
|
|
number: 3,
|
|
type: 11,
|
|
typeName: ".capture.Message.Child",
|
|
label: 1,
|
|
},
|
|
],
|
|
nestedType: [
|
|
{
|
|
name: "Child",
|
|
field: [{ name: "value", number: 1, type: 5, label: 1 }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
const retainedDetailsSchema = z.object({
|
|
partial_observation: z.strictObject({
|
|
kind: z.literal("retained-evidence"),
|
|
evidence_id: z.string(),
|
|
}),
|
|
});
|
|
|
|
cliTest(
|
|
"refuses ambiguous descriptor field identities through CLI/MCP while retaining source Evidence",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-protobuf-field-identity-");
|
|
const capture = join(root, "capture.har");
|
|
const text = protobufHar(body, "application/x-protobuf");
|
|
await writeFile(capture, text);
|
|
const call = await cli.sharedMcpCall!();
|
|
for (const variant of ["forward", "reverse", "nested", "oneof"]) {
|
|
const definition = descriptorDefinition();
|
|
const message = definition.file[0]!.messageType[0]!;
|
|
if (variant === "nested") {
|
|
message.nestedType[0]!.field.push(
|
|
create(FieldDescriptorProtoSchema, {
|
|
name: "alias",
|
|
number: 1,
|
|
type: 5,
|
|
label: 1,
|
|
}),
|
|
);
|
|
} else if (variant === "oneof") {
|
|
message.oneofDecl.push(
|
|
create(OneofDescriptorProtoSchema, { name: "choice" }),
|
|
);
|
|
message.field.push(
|
|
create(FieldDescriptorProtoSchema, {
|
|
name: "alias",
|
|
number: 1,
|
|
type: 5,
|
|
label: 1,
|
|
oneofIndex: 0,
|
|
}),
|
|
);
|
|
} else {
|
|
message.field[1]!.number = 1;
|
|
if (variant === "reverse") message.field.reverse();
|
|
}
|
|
const descriptor = join(root, `${variant}.pb`);
|
|
const bytes = toBinary(FileDescriptorSetSchema, definition);
|
|
await writeFile(descriptor, bytes);
|
|
const output = await cli.sharedCliRun!({
|
|
arguments: [
|
|
"inspect-web-network-capture",
|
|
capture,
|
|
"har",
|
|
"--record",
|
|
"0",
|
|
"--protobuf-schema",
|
|
descriptor,
|
|
"--protobuf-message",
|
|
"capture.Message",
|
|
"--json",
|
|
],
|
|
});
|
|
const expected = {
|
|
code: "invalid_request",
|
|
details: {
|
|
issues: [
|
|
{
|
|
path: ["protobuf", "schema", "descriptor_set_path"],
|
|
reason: "invalid_format",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
expect(output.exitCode, variant).toBe(1);
|
|
expect(output.json, variant).toMatchObject(expected);
|
|
const response = await call("inspect_web_network_capture", {
|
|
capture_path: capture,
|
|
format: "har",
|
|
record_ordinals: [0],
|
|
protobuf: {
|
|
schema: {
|
|
descriptor_set_path: descriptor,
|
|
message_type: "capture.Message",
|
|
},
|
|
},
|
|
});
|
|
const error = parseMcpToolError(response).error;
|
|
expect(error, variant).toMatchObject(expected);
|
|
const source = retainedDetailsSchema.parse(
|
|
error.details,
|
|
).partial_observation;
|
|
const retained = await call("inspect_analysis_view", {
|
|
source,
|
|
view: { kind: "summary" },
|
|
});
|
|
expect(retained.isError, JSON.stringify(retained)).not.toBe(true);
|
|
expect(
|
|
analysisViewResultSchema.parse(
|
|
parseEvidence(retained.structuredContent).normalized_result,
|
|
),
|
|
).toMatchObject({
|
|
summary: {
|
|
artifact: {
|
|
path: capture,
|
|
bytes: Buffer.byteLength(text),
|
|
sha256: sha256(text),
|
|
},
|
|
retained_records: 1,
|
|
protobuf_coverage: { status: "partial", inspected_records: 0 },
|
|
},
|
|
});
|
|
expect(
|
|
Buffer.compare(await readFile(descriptor), Buffer.from(bytes)),
|
|
).toBe(0);
|
|
}
|
|
expect(await readFile(capture, "utf8")).toBe(text);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"keeps repeated wire occurrences and message-local number reuse through CLI/MCP",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-protobuf-field-control-");
|
|
const capture = join(root, "capture.har");
|
|
const descriptor = join(root, "valid.pb");
|
|
const definition = toBinary(
|
|
FileDescriptorSetSchema,
|
|
descriptorDefinition(),
|
|
);
|
|
await writeFile(capture, protobufHar(body, "application/x-protobuf"));
|
|
await writeFile(descriptor, definition);
|
|
const output = await cli.sharedCliRun!({
|
|
arguments: [
|
|
"inspect-web-network-capture",
|
|
capture,
|
|
"har",
|
|
"--record",
|
|
"0",
|
|
"--protobuf-schema",
|
|
descriptor,
|
|
"--protobuf-message",
|
|
"capture.Message",
|
|
"--json",
|
|
],
|
|
});
|
|
expect(output.exitCode, output.stdout + output.stderr).toBe(0);
|
|
const evidence = parseEvidence(output.json);
|
|
const report = webNetworkCaptureSchema.parse(evidence.normalized_result);
|
|
expect(report.protobuf_coverage?.status).toBe("complete");
|
|
const decoded = report.records[0]?.protobuf?.[0];
|
|
expect(decoded?.schema).toMatchObject({
|
|
descriptor_set_path: descriptor,
|
|
descriptor_set_sha256: sha256(definition),
|
|
descriptor_set_bytes: definition.length,
|
|
message_type: "capture.Message",
|
|
});
|
|
expect(decoded?.fields).toMatchObject([
|
|
{
|
|
field_id: "offset:0",
|
|
number: 1,
|
|
schema_name: "first",
|
|
schema_value: "1",
|
|
},
|
|
{
|
|
field_id: "offset:2",
|
|
number: 1,
|
|
schema_name: "first",
|
|
schema_value: "2",
|
|
},
|
|
{
|
|
field_id: "offset:4",
|
|
number: 2,
|
|
schema_name: "second",
|
|
schema_value: "3",
|
|
},
|
|
{
|
|
field_id: "offset:6",
|
|
number: 3,
|
|
schema_name: "child",
|
|
schema_type: "capture.Message.Child",
|
|
},
|
|
{
|
|
field_id: "offset:8",
|
|
number: 1,
|
|
schema_name: "value",
|
|
schema_value: "4",
|
|
},
|
|
]);
|
|
expect(decoded?.fields).toHaveLength(5);
|
|
const call = await cli.sharedMcpCall!();
|
|
const response = await call("inspect_web_network_capture", {
|
|
capture_path: capture,
|
|
format: "har",
|
|
record_ordinals: [0],
|
|
protobuf: {
|
|
schema: {
|
|
descriptor_set_path: descriptor,
|
|
message_type: "capture.Message",
|
|
},
|
|
},
|
|
});
|
|
expect(response.isError, JSON.stringify(response)).not.toBe(true);
|
|
expect(parseEvidence(response.structuredContent).evidence_id).toBe(
|
|
evidence.evidence_id,
|
|
);
|
|
expect(
|
|
Buffer.compare(await readFile(descriptor), Buffer.from(definition)),
|
|
).toBe(0);
|
|
},
|
|
);
|