fix(contracts): advertise NUL with a portable hex escape

Use the equivalent hex NUL spelling in named public contracts while preserving current path and reserved-name validation. Verify converted JSON Schema and real stdio catalog consumers.

Closes #1329

Co-authored-by: Yi-111-a <97448247+Yi-111-a@users.noreply.github.com>
This commit is contained in:
yi111andYi-111-a authored and GitHub committed 2026-10-10 01:54:42 +08:00
1 parent 5f16124a31
commit 3f59a84bd1
7 files changed
+27 -18

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ export const swiftSymbolsSchema = z
.string()
.min(1)
.regex(/^[^\n]*$/u, "Each Swift symbol must be one line.")
.regex(/^[^\u0000]*$/u, "Swift symbols cannot contain NUL."),
.regex(/^[^\x00]*$/u, "Swift symbols cannot contain NUL."),
)
.min(1);
+1 -1
View File
@@ -25,7 +25,7 @@ import {
* patterns avoid look-around, which RE2 and Rust validators reject.
*/
const ROOT_RELATIVE_PATH =
/^(?:[^\x2f\\\u0000.][^\x2f\\\u0000]*|\.[^\x2f\\\u0000.][^\x2f\\\u0000]*|\.\.[^\x2f\\\u0000]+)(?:\x2f(?:[^\x2f\\\u0000.][^\x2f\\\u0000]*|\.[^\x2f\\\u0000.][^\x2f\\\u0000]*|\.\.[^\x2f\\\u0000]+))*$/u;
/^(?:[^\x2f\\\x00.][^\x2f\\\x00]*|\.[^\x2f\\\x00.][^\x2f\\\x00]*|\.\.[^\x2f\\\x00]+)(?:\x2f(?:[^\x2f\\\x00.][^\x2f\\\x00]*|\.[^\x2f\\\x00.][^\x2f\\\x00]*|\.\.[^\x2f\\\x00]+))*$/u;
const rootRelativePathSchema = z
.string()
.min(1)
@@ -81,7 +81,7 @@ export const electronActiveObservationInputSchema = z.strictObject({
.array(
z
.string()
.regex(/^[^\u0000]*$/u, "Electron arguments must not contain NUL"),
.regex(/^[^\x00]*$/u, "Electron arguments must not contain NUL"),
)
.default([]),
actions: z.array(actionSchema).default([]),
+10 -2
View File
@@ -1,11 +1,19 @@
import { isAbsolute, relative, sep } from "node:path";
import { z } from "zod";
/** Preserve selected filesystem names while rejecting OS-invalid NUL bytes. */
/**
* Preserve selected filesystem names while rejecting OS-invalid NUL bytes.
*
* The pattern spells NUL as `\x00` rather than `\u0000`. Both accept exactly the
* same strings. The hex form also avoids the short `\0` spelling rejected by
* strict schema consumers. A client can reject its entire tool request when
* one advertised pattern cannot compile. See `offsetDateTimeSchema` for the
* same hex-escape convention.
*/
export const localPathStringSchema = z
.string()
.min(1)
.regex(/^[^\u0000]*$/u, "Local filesystem paths cannot contain NUL");
.regex(/^[^\x00]*$/u, "Local filesystem paths cannot contain NUL");
/** Test lexical containment on the host platform; callers must resolve symlinks first. */
export const isPathWithinRoot = (root: string, path: string): boolean => {
+3 -5
View File
@@ -5,7 +5,7 @@ const hexSchema = z.string().regex(/^0x[0-9a-f]+$/u);
const nativeString = z
.string()
.regex(
/^[^\u0000]*$/u,
/^[^\x00]*$/u,
"Native launch and breakpoint strings cannot contain NUL",
);
/** Objective-C class names and selectors never contain spaces or brackets. */
@@ -61,10 +61,8 @@ export const nativeCallObservationInputSchema = z.strictObject({
z
.string()
.min(1)
.regex(/^[^=\u0000]+$/u, "Environment names cannot contain '=' or NUL"),
z
.string()
.regex(/^[^\u0000]*$/u, "Environment values cannot contain NUL"),
.regex(/^[^=\x00]+$/u, "Environment names cannot contain '=' or NUL"),
z.string().regex(/^[^\x00]*$/u, "Environment values cannot contain NUL"),
)
.default({})
.describe("Overrides on top of the environment REA runs with."),
+3 -3
View File
@@ -5,7 +5,7 @@ import { z } from "zod";
const positiveBudget = z.number().int().safe().positive();
const timedEventBase = { at_ms: z.number().int().safe().nonnegative() };
const reservedRunIdEnvironmentName = "REA_PROCESS_RUN_ID";
const ENVIRONMENT_NAME_CHARACTER = "[^=\\u0000]";
const ENVIRONMENT_NAME_CHARACTER = "[^=\\x00]";
/**
* Nonempty environment names other than the reserved run identifier. The
@@ -20,7 +20,7 @@ const environmentNamePattern = (reserved: string): RegExp => {
const character = reserved[index] ?? "";
// Diverge here, match the next reserved character, or (after a nonempty
// prefix) end the name early.
const choice = `[^=\\u0000${character}]${ENVIRONMENT_NAME_CHARACTER}*|${character}${rest}`;
const choice = `[^=\\x00${character}]${ENVIRONMENT_NAME_CHARACTER}*|${character}${rest}`;
rest = index === 0 ? choice : `(?:${choice})?`;
}
return new RegExp(`^(?:${rest})$`, "u");
@@ -38,7 +38,7 @@ const environmentName = z
const childProcessString = z
.string()
.regex(
/^[^\u0000]*$/u,
/^[^\x00]*$/u,
"Values passed to operating-system APIs cannot contain NUL",
);
export const normalizationSchema = z.object({
@@ -79,9 +79,12 @@ it("advertises portable NUL escapes and patterns that compile in all JS modes",
).toEqual([]);
// DeepSeek rejects the short NUL escape even though V8 accepts it in all
// three modes. Schema compilation must not hide that interchange failure.
expect(patterns.filter(({ pattern }) => pattern.includes("\\0"))).toEqual([]);
expect(patterns.some(({ pattern }) => pattern.includes("\\u0000"))).toBe(
true,
);
// three modes. Keep the shared hex spelling across the advertised catalog;
// actual provider acceptance is verified separately from JS compilation.
expect(
patterns.filter(
({ pattern }) => pattern.includes("\\0") || pattern.includes("\\u0000"),
),
).toEqual([]);
expect(patterns.some(({ pattern }) => pattern.includes("\\x00"))).toBe(true);
});