mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
fix(capture): retain safe caller path selections (#9626)
* fix(cli)!: retain repeated WASM input paths BREAKING CHANGE: caller_input_paths values may now be ordered arrays of strings as well as strings. Clients must accept both forms. * fix(capture): retain safe caller path selections * fix(evidence): retain auxiliary selections in direct summaries * test(wasm): separate SDK schema and acquired-input coverage
This commit is contained in:
1 parent
dda2d40295
commit
bf271ba3fb
10 files changed
+326
-130
No files matched your search
@@ -138,7 +138,9 @@ export const deliverAnalysisEvidence = async (
|
||||
// Later inspect_analysis_view requests are separate questions and do not
|
||||
// inherit the parent's selected input path.
|
||||
const selected =
|
||||
detail === "summary" && evidence.caller_input_path !== undefined
|
||||
detail === "summary" &&
|
||||
(evidence.caller_input_path !== undefined ||
|
||||
evidence.caller_input_paths !== undefined)
|
||||
? withEvidenceCallerInputPath(
|
||||
delivered.value,
|
||||
evidence.caller_input_path,
|
||||
|
||||
@@ -210,18 +210,23 @@ it("projects completed raw capture facts on failure through the caller's selecti
|
||||
});
|
||||
const result = await new WebNetworkCaptureService({
|
||||
inspect: () => Promise.resolve({ ok: false, error }),
|
||||
}).inspect({
|
||||
capture_path: report.artifact.path,
|
||||
format: "har",
|
||||
record_ordinals: [0],
|
||||
sensitive_values: ["private-marker"],
|
||||
});
|
||||
}).inspect(
|
||||
{
|
||||
capture_path: report.artifact.path,
|
||||
format: "har",
|
||||
record_ordinals: [0],
|
||||
sensitive_values: ["private-marker"],
|
||||
},
|
||||
undefined,
|
||||
{ capture: "./capture.har" },
|
||||
);
|
||||
if (result.ok) throw new Error("Cancellation must remain the primary result");
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
const partial = parseEvidence(result.error.partialObservation);
|
||||
expect(partial).toMatchObject({
|
||||
operation: "inspect_web_network_capture",
|
||||
caller_input_path: "./capture.har",
|
||||
parameters: { record_ordinals: [0] },
|
||||
subject: { local_path: "" },
|
||||
normalized_result: {
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { createAnalysisProfile } from "../domain/analysisProfile.js";
|
||||
import { createEvidence, type Evidence } from "../domain/evidence.js";
|
||||
import {
|
||||
createEvidence,
|
||||
withEvidenceCallerInputPath,
|
||||
type Evidence,
|
||||
} from "../domain/evidence.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import { jsonObjectSchema } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
@@ -39,6 +43,7 @@ export class WebNetworkCaptureService {
|
||||
async inspect(
|
||||
rawInput: unknown,
|
||||
options?: ExecutionOptions,
|
||||
callerPaths?: { readonly capture: string; readonly descriptor?: string },
|
||||
): Promise<Result<Evidence, AnalysisError>> {
|
||||
if (options?.signal?.aborted)
|
||||
return err(
|
||||
@@ -87,7 +92,11 @@ export class WebNetworkCaptureService {
|
||||
);
|
||||
let partialFailure: AnalysisError | undefined;
|
||||
if (partial.success) {
|
||||
const projected = this.#projectCapture(input, partial.data);
|
||||
const projected = this.#projectCapture(
|
||||
input,
|
||||
partial.data,
|
||||
callerPaths,
|
||||
);
|
||||
if (projected.ok) loaded.error.partialObservation = projected.value;
|
||||
else partialFailure = projected.error;
|
||||
} else if (isCapturePartialCandidate(loaded.error.partialObservation)) {
|
||||
@@ -128,7 +137,7 @@ export class WebNetworkCaptureService {
|
||||
"Capture adapter returned malformed historical evidence.",
|
||||
),
|
||||
);
|
||||
const projected = this.#projectCapture(input, report.data);
|
||||
const projected = this.#projectCapture(input, report.data, callerPaths);
|
||||
if (!projected.ok) return failure(projected.error);
|
||||
return completeProviderEvidence(projected.value, options?.signal);
|
||||
}
|
||||
@@ -136,6 +145,7 @@ export class WebNetworkCaptureService {
|
||||
#projectCapture(
|
||||
input: InspectWebNetworkCaptureInput,
|
||||
value: WebNetworkCapture,
|
||||
callerPaths?: { readonly capture: string; readonly descriptor?: string },
|
||||
): Result<Evidence, AnalysisError> {
|
||||
const artifactSha256 = value.artifact.sha256;
|
||||
if (artifactSha256 === null)
|
||||
@@ -160,6 +170,8 @@ export class WebNetworkCaptureService {
|
||||
const safeValue = excludeCaptureCoordinates(value, input.sensitive_values);
|
||||
const redactText = (text: string): string =>
|
||||
redactExplicitText(text, input.sensitive_values);
|
||||
const isSensitive = (text: string): boolean =>
|
||||
input.sensitive_values.some((literal) => text.includes(literal));
|
||||
const selected =
|
||||
input.record_ordinals ?? value.records.map(({ ordinal }) => ordinal);
|
||||
const records = [];
|
||||
@@ -189,16 +201,8 @@ export class WebNetworkCaptureService {
|
||||
limitations: record.limitations.map(redactText),
|
||||
});
|
||||
}
|
||||
const safePath = input.sensitive_values.some((literal) =>
|
||||
input.capture_path.includes(literal),
|
||||
)
|
||||
? ""
|
||||
: input.capture_path;
|
||||
const safeSha256 = input.sensitive_values.some((literal) =>
|
||||
artifactSha256.includes(literal),
|
||||
)
|
||||
? null
|
||||
: artifactSha256;
|
||||
const safePath = isSensitive(input.capture_path) ? "" : input.capture_path;
|
||||
const safeSha256 = isSensitive(artifactSha256) ? null : artifactSha256;
|
||||
const subjectUnavailableReason = redactText(
|
||||
"The explicitly sensitive artifact digest is excluded; its cryptographic identity is unknown in this Evidence.",
|
||||
);
|
||||
@@ -212,11 +216,7 @@ export class WebNetworkCaptureService {
|
||||
].map(redactText);
|
||||
const parameters = jsonObjectSchema.parse({
|
||||
capture_path: safePath,
|
||||
...(input.sensitive_values.some((literal) =>
|
||||
input.format.includes(literal),
|
||||
)
|
||||
? {}
|
||||
: { format: input.format }),
|
||||
...(isSensitive(input.format) ? {} : { format: input.format }),
|
||||
record_ordinals: selected,
|
||||
...(input.protobuf === undefined
|
||||
? {}
|
||||
@@ -247,48 +247,60 @@ export class WebNetworkCaptureService {
|
||||
? {}
|
||||
: { capture_timeout_ms: input.capture_timeout_ms }),
|
||||
});
|
||||
return ok(
|
||||
createEvidence(
|
||||
safeSha256 === null
|
||||
? undefined
|
||||
: {
|
||||
path: safePath,
|
||||
sha256: safeSha256,
|
||||
format: "file",
|
||||
},
|
||||
value.decoder,
|
||||
{
|
||||
operation: OPERATION,
|
||||
parameters,
|
||||
result: {
|
||||
...safeValue,
|
||||
artifact: {
|
||||
...value.artifact,
|
||||
path: safePath,
|
||||
sha256: safeSha256,
|
||||
},
|
||||
records,
|
||||
limitations:
|
||||
safeSha256 === null
|
||||
? [...limitations, subjectUnavailableReason]
|
||||
: limitations,
|
||||
const evidence = createEvidence(
|
||||
safeSha256 === null
|
||||
? undefined
|
||||
: {
|
||||
path: safePath,
|
||||
sha256: safeSha256,
|
||||
format: "file",
|
||||
},
|
||||
...(safeValue.format !== "qlog"
|
||||
? {}
|
||||
: {
|
||||
analysisProfile: createAnalysisProfile(value.decoder, {
|
||||
qlog_profile: safeValue.qlog.profile,
|
||||
clock: safeValue.qlog.clock,
|
||||
scope: safeValue.qlog.scope,
|
||||
}),
|
||||
}),
|
||||
confidence: "observed",
|
||||
authority: "historical-reference",
|
||||
limitations,
|
||||
...(safeSha256 === null ? { subjectUnavailableReason } : {}),
|
||||
locations:
|
||||
safePath === "" ? [] : [{ kind: "artifact-path", path: safePath }],
|
||||
value.decoder,
|
||||
{
|
||||
operation: OPERATION,
|
||||
parameters,
|
||||
result: {
|
||||
...safeValue,
|
||||
artifact: {
|
||||
...value.artifact,
|
||||
path: safePath,
|
||||
sha256: safeSha256,
|
||||
},
|
||||
records,
|
||||
limitations:
|
||||
safeSha256 === null
|
||||
? [...limitations, subjectUnavailableReason]
|
||||
: limitations,
|
||||
},
|
||||
...(safeValue.format !== "qlog"
|
||||
? {}
|
||||
: {
|
||||
analysisProfile: createAnalysisProfile(value.decoder, {
|
||||
qlog_profile: safeValue.qlog.profile,
|
||||
clock: safeValue.qlog.clock,
|
||||
scope: safeValue.qlog.scope,
|
||||
}),
|
||||
}),
|
||||
confidence: "observed",
|
||||
authority: "historical-reference",
|
||||
limitations,
|
||||
...(safeSha256 === null ? { subjectUnavailableReason } : {}),
|
||||
locations:
|
||||
safePath === "" ? [] : [{ kind: "artifact-path", path: safePath }],
|
||||
},
|
||||
);
|
||||
if (callerPaths === undefined) return ok(evidence);
|
||||
const safeCallerPath = (path: string | undefined) =>
|
||||
path === undefined || isSensitive(path) ? undefined : path;
|
||||
const callerPath = safeCallerPath(callerPaths.capture);
|
||||
const descriptor = safeCallerPath(callerPaths.descriptor);
|
||||
return ok(
|
||||
withEvidenceCallerInputPath(
|
||||
evidence,
|
||||
callerPath,
|
||||
descriptor === undefined
|
||||
? undefined
|
||||
: { descriptor_set_path: descriptor },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,7 +258,9 @@ export const publishCliAnalysisEvidence = async (
|
||||
// This projection answers the same direct invocation as its parent. Later
|
||||
// inspect_analysis_view calls own their own input and do not inherit it.
|
||||
const selected =
|
||||
detail === "summary" && complete.caller_input_path !== undefined
|
||||
detail === "summary" &&
|
||||
(complete.caller_input_path !== undefined ||
|
||||
complete.caller_input_paths !== undefined)
|
||||
? withEvidenceCallerInputPath(
|
||||
delivered.value,
|
||||
complete.caller_input_path,
|
||||
|
||||
@@ -111,6 +111,12 @@ export const registerWebNetworkCaptureCommands = (
|
||||
: { capture_timeout_ms: options.captureTimeoutMs }),
|
||||
},
|
||||
{ signal },
|
||||
{
|
||||
capture: args.capturePath,
|
||||
...(options.protobufSchema === undefined
|
||||
? {}
|
||||
: { descriptor: options.protobufSchema }),
|
||||
},
|
||||
),
|
||||
service,
|
||||
providerId: "web-network-capture",
|
||||
|
||||
@@ -922,3 +922,24 @@ it("borrows variable-size collections for admission", () => {
|
||||
expect(envelope.locations).toBe(locations);
|
||||
expect(envelope.evidence_links).toBe(evidenceLinks);
|
||||
});
|
||||
|
||||
it("retains safe auxiliary paths when the direct caller path is unavailable", () => {
|
||||
const original = createEvidence(TARGET, PROVIDER, {
|
||||
operation: "inspect",
|
||||
parameters: {},
|
||||
result: null,
|
||||
});
|
||||
const rebound = withEvidenceCallerInputPath(original, undefined, {
|
||||
descriptor_set_path: "./schema.pb",
|
||||
});
|
||||
expect(rebound.caller_input_path).toBeUndefined();
|
||||
expect(rebound.caller_input_paths).toEqual({
|
||||
descriptor_set_path: "./schema.pb",
|
||||
});
|
||||
expect(rebound.evidence_id).toBe(original.evidence_id);
|
||||
expect(() =>
|
||||
withEvidenceCallerInputPath(original, undefined, {
|
||||
descriptor_set_path: "bad\0path",
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
+12
-4
@@ -378,10 +378,11 @@ export const rebindEvidenceToTargetPath = (
|
||||
/** Bind the exact selected request path without changing semantic identity or stored history. */
|
||||
export const withEvidenceCallerInputPath = (
|
||||
evidence: Evidence,
|
||||
rawPath: string,
|
||||
rawPath: string | undefined,
|
||||
additionalPaths?: CallerInputPaths,
|
||||
): Evidence => {
|
||||
const callerInputPath = nulFreeStringSchema.parse(rawPath);
|
||||
const callerInputPath =
|
||||
rawPath === undefined ? undefined : nulFreeStringSchema.parse(rawPath);
|
||||
const owned = immutableEvidence(evidence);
|
||||
const paths =
|
||||
additionalPaths === undefined
|
||||
@@ -389,12 +390,19 @@ export const withEvidenceCallerInputPath = (
|
||||
: callerInputPathsSchema.parse(additionalPaths);
|
||||
const samePaths =
|
||||
paths === undefined || isDeepStrictEqual(owned.caller_input_paths, paths);
|
||||
if (owned.caller_input_path === callerInputPath && samePaths) return owned;
|
||||
if (
|
||||
(callerInputPath === undefined ||
|
||||
owned.caller_input_path === callerInputPath) &&
|
||||
samePaths
|
||||
)
|
||||
return owned;
|
||||
// The authenticated source owns its immutable payloads. Only this small
|
||||
// envelope changes, so replay does not copy a document-sized result.
|
||||
const rebound = {
|
||||
...owned,
|
||||
caller_input_path: callerInputPath,
|
||||
...(callerInputPath === undefined
|
||||
? {}
|
||||
: { caller_input_path: callerInputPath }),
|
||||
...(paths === undefined ? {} : { caller_input_paths: paths }),
|
||||
};
|
||||
return rememberReboundEvidence(owned, rebound);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
protobufFrame,
|
||||
protobufCaptureBody,
|
||||
} from "../../fixtures/protobufCapture.js";
|
||||
import { expectCliMcpEvidenceParity } from "../../fixtures/cliMcpEvidenceParity.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import {
|
||||
webNetworkCaptureSchema,
|
||||
@@ -678,7 +679,7 @@ captureTest.for(
|
||||
protobuf: { framing: "auto" },
|
||||
});
|
||||
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
||||
expect(reply.structuredContent).toEqual(response.json);
|
||||
expectCliMcpEvidenceParity(response.json, reply.structuredContent, path);
|
||||
expect(await readFile(path, "utf8")).toBe(original);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -133,65 +133,80 @@ ${body}
|
||||
});
|
||||
|
||||
const { client, call } = await connectLocalToolsMcp(environment);
|
||||
const schema = await advertisedMcpToolSchemas(
|
||||
client,
|
||||
"inspect_wasm_artifact",
|
||||
);
|
||||
const valid = withEvidenceCallerInputPath(
|
||||
createEvidence(
|
||||
undefined,
|
||||
{ id: "fixture-wabt", name: "fixture", version: "1" },
|
||||
{
|
||||
operation: "inspect_wasm_artifact",
|
||||
parameters: {},
|
||||
result: wasmArtifactFixture(),
|
||||
confidence: "observed",
|
||||
},
|
||||
),
|
||||
"./selected.wasm",
|
||||
{
|
||||
candidate_paths: ["./candidate.wasm", "./candidate.wasm"],
|
||||
glue_paths: ["./glue.js"],
|
||||
},
|
||||
);
|
||||
expect(
|
||||
schema.ajv.validate(schema.outputSchema, valid),
|
||||
JSON.stringify(schema.ajv.errors),
|
||||
).toBe(true);
|
||||
const response = await call("inspect_wasm_artifact", {
|
||||
path: selected,
|
||||
candidate_paths: [candidate],
|
||||
glue_paths: [glue],
|
||||
});
|
||||
const mcpError = parseMcpToolError(response).error;
|
||||
expect(mcpError.code).toBe("invalid_request");
|
||||
const partialReference = z
|
||||
.object({
|
||||
kind: z.literal("retained-evidence"),
|
||||
evidence_id: z.string(),
|
||||
})
|
||||
.parse(
|
||||
z
|
||||
.object({ partial_observation: z.unknown() })
|
||||
.passthrough()
|
||||
.parse(mcpError.details).partial_observation,
|
||||
try {
|
||||
const response = await call("inspect_wasm_artifact", {
|
||||
path: selected,
|
||||
candidate_paths: [candidate],
|
||||
glue_paths: [glue],
|
||||
});
|
||||
const mcpError = parseMcpToolError(response).error;
|
||||
expect(mcpError.code).toBe("invalid_request");
|
||||
const partialReference = z
|
||||
.object({
|
||||
kind: z.literal("retained-evidence"),
|
||||
evidence_id: z.string(),
|
||||
})
|
||||
.parse(
|
||||
z
|
||||
.object({ partial_observation: z.unknown() })
|
||||
.passthrough()
|
||||
.parse(mcpError.details).partial_observation,
|
||||
);
|
||||
const retainedReply = await call("get_evidence_bundle", {
|
||||
detail: "complete",
|
||||
});
|
||||
expect(retainedReply.isError).not.toBe(true);
|
||||
const retainedBundle = toolContract(
|
||||
"get_evidence_bundle",
|
||||
).outputSchema.parse(retainedReply.structuredContent).result;
|
||||
if ("kind" in retainedBundle)
|
||||
throw new Error("Expected complete retained Evidence");
|
||||
const retained = parseEvidence(
|
||||
retainedBundle.records.find(
|
||||
(record) => record.evidence_id === partialReference.evidence_id,
|
||||
),
|
||||
);
|
||||
const retainedReply = await call("get_evidence_bundle", {
|
||||
detail: "complete",
|
||||
});
|
||||
expect(retainedReply.isError).not.toBe(true);
|
||||
const retainedBundle = toolContract(
|
||||
"get_evidence_bundle",
|
||||
).outputSchema.parse(retainedReply.structuredContent).result;
|
||||
if ("kind" in retainedBundle)
|
||||
throw new Error("Expected complete retained Evidence");
|
||||
const retained = parseEvidence(
|
||||
retainedBundle.records.find(
|
||||
(record) => record.evidence_id === partialReference.evidence_id,
|
||||
),
|
||||
);
|
||||
expect(retained.evidence_id).toBe(partialReference.evidence_id);
|
||||
expect(retained.normalized_result).toMatchObject(expected);
|
||||
await client.ping();
|
||||
expect(retained.evidence_id).toBe(partialReference.evidence_id);
|
||||
expect(retained.normalized_result).toMatchObject(expected);
|
||||
await client.ping();
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"advertises ordered WASM caller selections through the SDK output schema",
|
||||
async () => {
|
||||
const { client } = await connectLocalToolsMcp();
|
||||
try {
|
||||
const schema = await advertisedMcpToolSchemas(
|
||||
client,
|
||||
"inspect_wasm_artifact",
|
||||
);
|
||||
const valid = withEvidenceCallerInputPath(
|
||||
createEvidence(
|
||||
undefined,
|
||||
{ id: "fixture-wabt", name: "fixture", version: "1" },
|
||||
{
|
||||
operation: "inspect_wasm_artifact",
|
||||
parameters: {},
|
||||
result: wasmArtifactFixture(),
|
||||
confidence: "observed",
|
||||
},
|
||||
),
|
||||
"./selected.wasm",
|
||||
{
|
||||
candidate_paths: ["./candidate.wasm", "./candidate.wasm"],
|
||||
glue_paths: ["./glue.js"],
|
||||
},
|
||||
);
|
||||
expect(
|
||||
schema.ajv.validate(schema.outputSchema, valid),
|
||||
JSON.stringify(schema.ajv.errors),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,124 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { deliverAnalysisEvidence } from "../../../src/application/EvidenceBundleCommands.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import {
|
||||
protobufCaptureDescriptor,
|
||||
protobufHar,
|
||||
} from "../../fixtures/protobufCapture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"retains selected capture paths without exposing declared sensitive values",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-capture-selected-");
|
||||
const cwd = join(root, "secret-parent");
|
||||
await mkdir(cwd);
|
||||
await writeFile(join(cwd, "capture.har"), protobufHar());
|
||||
await writeFile(join(cwd, "capture.pb"), protobufCaptureDescriptor());
|
||||
const args = [
|
||||
"inspect-web-network-capture",
|
||||
"./capture.har",
|
||||
"har",
|
||||
"--record",
|
||||
"0",
|
||||
"--protobuf-schema",
|
||||
"./capture.pb",
|
||||
"--protobuf-message",
|
||||
"capture.Message",
|
||||
"--json",
|
||||
];
|
||||
const result = await cli.run({ cwd, arguments: args });
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = parseEvidence(result.json);
|
||||
expect(evidence.caller_input_path).toBe("./capture.har");
|
||||
expect(evidence.caller_input_paths).toEqual({
|
||||
descriptor_set_path: "./capture.pb",
|
||||
});
|
||||
const parentExcluded = await cli.run({
|
||||
cwd,
|
||||
arguments: [...args, "--sensitive-value", "secret-parent"],
|
||||
});
|
||||
expect(parentExcluded.exitCode, parentExcluded.stderr).toBe(0);
|
||||
expect(JSON.stringify(parentExcluded.json)).not.toContain("secret-parent");
|
||||
expect(parseEvidence(parentExcluded.json).caller_input_path).toBe(
|
||||
"./capture.har",
|
||||
);
|
||||
const originalExcluded = await cli.run({
|
||||
cwd,
|
||||
arguments: [...args, "--sensitive-value", "capture.har"],
|
||||
});
|
||||
expect(originalExcluded.exitCode, originalExcluded.stderr).toBe(0);
|
||||
expect(JSON.stringify(originalExcluded.json)).not.toContain("capture.har");
|
||||
const safe = parseEvidence(originalExcluded.json);
|
||||
expect(safe.caller_input_path).toBeUndefined();
|
||||
expect(safe.caller_input_paths).toEqual({
|
||||
descriptor_set_path: "./capture.pb",
|
||||
});
|
||||
const summaryExcluded = await cli.run({
|
||||
cwd,
|
||||
arguments: [
|
||||
...args,
|
||||
"--sensitive-value",
|
||||
"capture.har",
|
||||
"--detail",
|
||||
"summary",
|
||||
"--evidence_output",
|
||||
"./safe-evidence.json",
|
||||
],
|
||||
});
|
||||
expect(summaryExcluded.exitCode, summaryExcluded.stderr).toBe(0);
|
||||
const summary = parseEvidence(
|
||||
(summaryExcluded.json as { summary: unknown }).summary,
|
||||
);
|
||||
expect(summary.caller_input_path).toBeUndefined();
|
||||
expect(summary.caller_input_paths).toEqual({
|
||||
descriptor_set_path: "./capture.pb",
|
||||
});
|
||||
expect(JSON.stringify(summaryExcluded.json)).not.toContain("capture.har");
|
||||
const applicationSummary = await deliverAnalysisEvidence(
|
||||
safe,
|
||||
join(cwd, "application-evidence.json"),
|
||||
"summary",
|
||||
);
|
||||
if (!applicationSummary.ok) throw applicationSummary.error;
|
||||
expect(applicationSummary.value.caller_input_path).toBeUndefined();
|
||||
expect(applicationSummary.value.caller_input_paths).toEqual({
|
||||
descriptor_set_path: "./capture.pb",
|
||||
});
|
||||
const descriptorExcluded = await cli.run({
|
||||
cwd,
|
||||
arguments: [...args, "--sensitive-value", "capture.pb"],
|
||||
});
|
||||
expect(descriptorExcluded.exitCode, descriptorExcluded.stderr).toBe(0);
|
||||
expect(JSON.stringify(descriptorExcluded.json)).not.toContain("capture.pb");
|
||||
const safeDescriptor = parseEvidence(descriptorExcluded.json);
|
||||
expect(safeDescriptor.caller_input_path).toBe("./capture.har");
|
||||
expect(safeDescriptor.caller_input_paths).toBeUndefined();
|
||||
const { client, call } = await connectLocalToolsMcp();
|
||||
try {
|
||||
const response = await call("inspect_web_network_capture", {
|
||||
capture_path: join(cwd, "capture.har"),
|
||||
format: "har",
|
||||
record_ordinals: [0],
|
||||
protobuf: {
|
||||
schema: {
|
||||
descriptor_set_path: join(cwd, "capture.pb"),
|
||||
message_type: "capture.Message",
|
||||
},
|
||||
},
|
||||
sensitive_values: ["secret-parent"],
|
||||
});
|
||||
expect(response.isError, JSON.stringify(response)).not.toBe(true);
|
||||
expect(JSON.stringify(response)).not.toContain("secret-parent");
|
||||
expect(
|
||||
parseEvidence(response.structuredContent).caller_input_path,
|
||||
).toBeUndefined();
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user