Four web tools accepted caller-selected local paths as plain strings, so a NUL byte passed contract validation and reached filesystem calls. The services only checked isAbsolute, and isAbsolute admits a NUL, so the failure surfaced later as a raw ERR_INVALID_ARG_VALUE instead of a named input constraint. Every other local-input tool already refuses NUL through the shared local-path schema, and the existing OS-invalid-local-input harness covers those but not these web tools. inspect_web_network_capture.capture_path, export_web_scripts.capture_path and output_directory, trace_web_source_location.manifest_path and source_map.path, and trace_web_module_imports.manifest_path and import_map.path now use localPathStringSchema. Each field already required a nonempty string, so the advertised pattern and minLength stay the same and only the NUL refusal is added. Validation: a new schema test rejects NUL in all seven fields while accepting ordinary absolute paths; the four tools are added to the OS-invalid-local-input harness and reject NUL through the real advertised MCP contracts; typecheck, oxlint, oxfmt, and toolSchemaValidity with a regenerated catalog. Two webScriptExport boundary cases fail identically with and without this change on this Windows host (symlink EPERM and private-runtime-root authority), so they are environmental.
142 lines
4.6 KiB
TypeScript
142 lines
4.6 KiB
TypeScript
import { z } from "zod";
|
|
import { jsonValueSchema } from "./jsonValue.js";
|
|
import { localPathStringSchema } from "./localPath.js";
|
|
|
|
/** Complete historical evidence budgets; exceeding a budget returns no partial capture. */
|
|
export const WEB_NETWORK_CAPTURE_LIMITS = {
|
|
inputBytes: 32 * 1024 * 1024,
|
|
outputBytes: 96 * 1024 * 1024,
|
|
timeoutMs: 30_000,
|
|
depth: 64,
|
|
} as const;
|
|
|
|
/** Inspect an explicit retained capture without contacting its recorded destinations. */
|
|
export const inspectWebNetworkCaptureInputSchema = z.strictObject({
|
|
capture_path: localPathStringSchema.describe(
|
|
"Absolute local HAR or mitmproxy capture path.",
|
|
),
|
|
format: z.enum(["har", "mitmproxy"]),
|
|
record_ordinals: z
|
|
.array(z.number().int().nonnegative())
|
|
.min(1)
|
|
.optional()
|
|
.describe(
|
|
"Zero-based producer record ordinals, in caller-selected order. Omit to inspect every record.",
|
|
),
|
|
sensitive_values: z
|
|
.array(z.string().min(1))
|
|
.default([])
|
|
.describe(
|
|
"Literal text excluded from capture payloads, byte/numeric projections, producer coordinates, artifact paths/digests and diagnostic text. Declaration strings are never persisted in Evidence parameters. Canonical contract keys, enums and provider identities remain intact.",
|
|
),
|
|
});
|
|
|
|
const pointerSchema = z
|
|
.string()
|
|
.describe("RFC 6901 pointer in the producer's reported object.");
|
|
const redactionSchema = z.union([
|
|
z.strictObject({
|
|
pointer: pointerSchema,
|
|
reason: z.enum(["transport-credential", "explicit-sensitive-value"]),
|
|
}),
|
|
z.strictObject({
|
|
pointer: pointerSchema,
|
|
reason: z.literal("explicit-sensitive-value"),
|
|
scope: z
|
|
.literal("property-name")
|
|
.describe(
|
|
"An omitted property name; pointer identifies its actual parent, not an invented child coordinate.",
|
|
),
|
|
}),
|
|
]);
|
|
const binaryBase = {
|
|
pointer: pointerSchema,
|
|
representation: z.enum(["producer-bytes", "har-base64-content"]),
|
|
};
|
|
const binarySchema = z.discriminatedUnion("state", [
|
|
z.strictObject({
|
|
...binaryBase,
|
|
state: z.literal("retained"),
|
|
content_base64: z.string(),
|
|
bytes: z.number().int().nonnegative(),
|
|
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
}),
|
|
z.strictObject({
|
|
...binaryBase,
|
|
state: z.literal("redacted"),
|
|
content_base64: z.null(),
|
|
bytes: z.null(),
|
|
sha256: z.null(),
|
|
}),
|
|
]);
|
|
const numberSchema = z.strictObject({
|
|
pointer: pointerSchema,
|
|
producer_type: z.enum(["json-number", "integer", "float"]),
|
|
literal: z.string(),
|
|
});
|
|
|
|
/** Preserve producer representations with independent sidecars rather than invented browser IDs. */
|
|
export const webNetworkCaptureRecordSchema = z.strictObject({
|
|
ordinal: z.number().int().nonnegative(),
|
|
location: z.discriminatedUnion("kind", [
|
|
z.strictObject({
|
|
kind: z.literal("unknown"),
|
|
reason: z.literal("explicit-sensitive-value"),
|
|
}),
|
|
z.strictObject({ kind: z.literal("json-pointer"), pointer: pointerSchema }),
|
|
z.strictObject({
|
|
kind: z.literal("byte-range"),
|
|
offset: z.number().int().nonnegative(),
|
|
bytes: z.number().int().positive(),
|
|
}),
|
|
]),
|
|
reported: jsonValueSchema,
|
|
binary_fields: z.array(binarySchema),
|
|
numeric_literals: z.array(numberSchema),
|
|
redactions: z.array(redactionSchema),
|
|
limitations: z.array(z.string()),
|
|
});
|
|
|
|
/** Historical capture inspection has file identity, producer provenance and explicit unknowns. */
|
|
export const webNetworkCaptureSchema = z.strictObject({
|
|
artifact: z.strictObject({
|
|
path: z
|
|
.string()
|
|
.describe(
|
|
"Selected artifact path; empty when explicitly excluded from Evidence.",
|
|
),
|
|
sha256: z
|
|
.string()
|
|
.regex(/^[0-9a-f]{64}$/)
|
|
.nullable()
|
|
.describe(
|
|
"Observed artifact SHA-256; null only when explicitly excluded by a sensitive declaration.",
|
|
),
|
|
bytes: z.number().int().nonnegative(),
|
|
}),
|
|
format: z.enum(["har", "mitmproxy"]),
|
|
decoder: z.strictObject({
|
|
id: z.string().min(1),
|
|
name: z.string().min(1),
|
|
version: z.string().min(1),
|
|
}),
|
|
container: z.strictObject({
|
|
reported: jsonValueSchema,
|
|
numeric_literals: z.array(numberSchema),
|
|
redactions: z.array(redactionSchema),
|
|
records_pointer: pointerSchema.nullable(),
|
|
}),
|
|
total_records: z.number().int().nonnegative(),
|
|
records: z.array(webNetworkCaptureRecordSchema),
|
|
runtime_attribution: z.literal("unknown"),
|
|
limitations: z.array(z.string()),
|
|
});
|
|
|
|
export type InspectWebNetworkCaptureInput = z.output<
|
|
typeof inspectWebNetworkCaptureInputSchema
|
|
>;
|
|
export type WebNetworkCapture = z.output<typeof webNetworkCaptureSchema>;
|
|
export type WebNetworkCaptureRecord = z.output<
|
|
typeof webNetworkCaptureRecordSchema
|
|
>;
|