Files
rea/src/domain/webSourceLocation.ts
T
DeryFerd 499ac18011 fix(web): reject NUL bytes in web tool local path inputs
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.
2026-10-11 23:32:52 +07:00

142 lines
4.9 KiB
TypeScript

import { z } from "zod";
import { digestSchema } from "./digests.js";
import { localPathStringSchema } from "./localPath.js";
import { exportedWebScriptSchema } from "./webScriptExport.js";
import {
capturedWebManifestIdentitySchema,
selectedWebScriptInputSchema,
webArtifactFileSchema,
} from "./webScriptArtifacts.js";
/** JavaScript source-map positions use UTF-16 columns and one-based lines. */
export const webSourcePositionSchema = z.strictObject({
line: z.number().int().min(1),
column: z.number().int().min(0),
});
/** Trace one explicit generated position through an explicitly paired local map. */
export const webSourceLocationInputSchema = selectedWebScriptInputSchema.extend(
{
source_map: z.strictObject({
path: localPathStringSchema.describe(
"Absolute local source-map path; no fetch",
),
url: z
.string()
.min(1)
.describe(
"Explicit source-map URL context used to resolve original source names",
),
}),
generated_position: webSourcePositionSchema,
},
);
export type WebSourceLocationInput = z.output<
typeof webSourceLocationInputSchema
>;
const originalContentSchema = z.discriminatedUnion("state", [
z.strictObject({
state: z.literal("embedded"),
text: z.string(),
utf8_sha256: digestSchema,
utf8_bytes: z.number().int().min(0),
representation: z.literal("UTF-8 encoding of decoded sourcesContent text"),
}),
z.strictObject({
state: z.literal("unavailable"),
reason: z.literal("sourcesContent-not-retained"),
}),
]);
/** Keep every matching segment, including anonymous mappings and duplicate URLs. */
export const webSourceMapMatchSchema = z.discriminatedUnion("state", [
z.strictObject({ state: z.literal("generated-only") }),
z.strictObject({
state: z.literal("mapped"),
flattened_source_index: z.number().int().min(0),
section_path: z.array(z.number().int().min(0)),
source_index: z.number().int().min(0),
reported_source: z.string().nullable(),
reported_source_root: z.string().nullable(),
resolved_url: z.string().nullable(),
ignored: z.boolean(),
content: originalContentSchema,
original_position: webSourcePositionSchema.extend({
offset: z.number().int().min(0).nullable(),
content_position: z.enum([
"verified-in-embedded-text",
"outside-embedded-text",
"content-unavailable",
]),
}),
name: z.string().nullable(),
}),
]);
/** Codec observations; map association and deployment authenticity remain selected context. */
export const webSourceMapReportSchema = z.strictObject({
source_map_sha256: digestSchema,
url_context: z.string().min(1),
engine: z.strictObject({ id: z.string().min(1), version: z.string().min(1) }),
runtime: z.discriminatedUnion("id", [
z.strictObject({
id: z.literal("node-v8"),
version: z.string().min(1),
v8_heap_limit_bytes: z.number().int().min(1),
}),
// Bun reports V8-shaped heap statistics but does not enforce V8 heap flags.
// On Windows REA instead enforces a Job Object commit limit on the codec.
z.strictObject({
id: z.literal("bun"),
version: z.string().min(1),
node_compatibility_version: z.string().min(1),
v8_heap_limit_bytes: z.null(),
process_memory_limit_bytes: z.number().int().min(1).nullable(),
}),
]),
format: z.enum(["regular", "indexed"]),
reported_file: z.string().nullable(),
lookup: z.strictObject({
requested: webSourcePositionSchema,
matched_generated_position: webSourcePositionSchema.nullable(),
semantics: z.literal(
"greatest generated position <= requested, across lines; all equal-position segments",
),
location_units: z.literal("one-based lines; zero-based UTF-16 columns"),
}),
matches: z.array(webSourceMapMatchSchema),
diagnostics: z.array(z.string()),
});
/** Bounds for complete source-map evidence and upstream decoded layouts. */
export const WEB_SOURCE_MAP_LIMITS = {
mapBytes: 4 * 1024 * 1024,
outputBytes: 32 * 1024 * 1024,
decodedRows: 262144,
decodedSegments: 262144,
decodedRecords: 262144,
sectionDepth: 64,
decodeTimeoutMs: 20000,
/** V8 old-generation heap for the codec, and its Bun process budget. */
codecHeapMiB: 192,
} as const;
export type WebSourceMapReport = z.output<typeof webSourceMapReportSchema>;
/** Selected capture/map byte identities and complete point attribution inline. */
export const webSourceLocationResultSchema = webSourceMapReportSchema.extend({
manifest: capturedWebManifestIdentitySchema,
source: webArtifactFileSchema.extend({
script_index: z.number().int().min(0),
script: exportedWebScriptSchema,
}),
source_map: webArtifactFileSchema.extend({
url: z.string().min(1),
association: z.literal("caller-selected"),
}),
generated_offset: z.number().int().min(0),
source_authenticity: z.literal("unknown"),
execution: z.literal("unknown"),
limitations: z.array(z.string()),
});