fix(mcp): emit portable Unicode NUL patterns (#1230)
Preserve NUL rejection while avoiding the short escape rejected by strict schema consumers. Guard the entire advertised input/output catalog and all three JavaScript regexp modes. Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
This commit is contained in:
parent
f4c99ab8a5
commit
aa5750c263
|
|
@ -71,7 +71,7 @@ export const swiftSymbolsSchema = z
|
|||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^\n]*$/u, "Each Swift symbol must be one line.")
|
||||
.regex(/^[^\0]*$/u, "Swift symbols cannot contain NUL."),
|
||||
.regex(/^[^\u0000]*$/u, "Swift symbols cannot contain NUL."),
|
||||
)
|
||||
.min(1);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
|
||||
/** Normalized path below the analyzed root: no `.`/`..` or empty segments. */
|
||||
const ROOT_RELATIVE_PATH =
|
||||
/^(?!\/)(?!(?:.*\/)?\.{1,2}(?:\/|$))(?!.*\/\/)(?!.*\/$)[^\\\0]+$/u;
|
||||
/^(?!\/)(?!(?:.*\/)?\.{1,2}(?:\/|$))(?!.*\/\/)(?!.*\/$)[^\\\u0000]+$/u;
|
||||
const rootRelativePathSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ export const electronActiveObservationInputSchema = z.strictObject({
|
|||
),
|
||||
args: z
|
||||
.array(
|
||||
z.string().regex(/^[^\0]*$/u, "Electron arguments must not contain NUL"),
|
||||
z
|
||||
.string()
|
||||
.regex(/^[^\u0000]*$/u, "Electron arguments must not contain NUL"),
|
||||
)
|
||||
.default([]),
|
||||
actions: z.array(actionSchema).default([]),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { z } from "zod";
|
|||
export const localPathStringSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^\0]*$/u, "Local filesystem paths cannot contain NUL");
|
||||
.regex(/^[^\u0000]*$/u, "Local filesystem paths cannot contain NUL");
|
||||
|
||||
/** True when the value is an absolute filesystem path on the host platform. */
|
||||
export const isAbsoluteLocalPath = (value: string): boolean =>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const hexSchema = z.string().regex(/^0x[0-9a-f]+$/u);
|
|||
const nativeString = z
|
||||
.string()
|
||||
.regex(
|
||||
/^[^\0]*$/u,
|
||||
/^[^\u0000]*$/u,
|
||||
"Native launch and breakpoint strings cannot contain NUL",
|
||||
);
|
||||
/** Objective-C class names and selectors never contain spaces or brackets. */
|
||||
|
|
@ -61,8 +61,10 @@ export const nativeCallObservationInputSchema = z.strictObject({
|
|||
z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^=\0]+$/u, "Environment names cannot contain '=' or NUL"),
|
||||
z.string().regex(/^[^\0]*$/u, "Environment values cannot contain NUL"),
|
||||
.regex(/^[^=\u0000]+$/u, "Environment names cannot contain '=' or NUL"),
|
||||
z
|
||||
.string()
|
||||
.regex(/^[^\u0000]*$/u, "Environment values cannot contain NUL"),
|
||||
)
|
||||
.default({})
|
||||
.describe("Overrides on top of the environment REA runs with."),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const reservedRunIdEnvironmentName = "REA_PROCESS_RUN_ID";
|
|||
const environmentName = z
|
||||
.string()
|
||||
.min(1, "Environment names must not be empty")
|
||||
.regex(/^(?!REA_PROCESS_RUN_ID(?![\s\S]))[^=\0]+$/u, {
|
||||
.regex(/^(?!REA_PROCESS_RUN_ID(?![\s\S]))[^=\u0000]+$/u, {
|
||||
error: (issue) =>
|
||||
issue.input === reservedRunIdEnvironmentName
|
||||
? `${reservedRunIdEnvironmentName} is reserved by the process adapter`
|
||||
|
|
@ -17,7 +17,7 @@ const environmentName = z
|
|||
const childProcessString = z
|
||||
.string()
|
||||
.regex(
|
||||
/^[^\0]*$/u,
|
||||
/^[^\u0000]*$/u,
|
||||
"Values passed to operating-system APIs cannot contain NUL",
|
||||
);
|
||||
export const normalizationSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const collectPatterns = (
|
|||
}
|
||||
};
|
||||
|
||||
it("advertises patterns that compile under every engine a client may use", async () => {
|
||||
it("advertises portable NUL escapes and patterns that compile in all JS modes", async () => {
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("No deep provider may start for a schema projection");
|
||||
});
|
||||
|
|
@ -63,15 +63,24 @@ it("advertises patterns that compile under every engine a client may use", async
|
|||
// class, so REA's own schema validation cannot observe the defect. `v` mode
|
||||
// rejects them, and a client that compiles advertised patterns rejects the
|
||||
// complete tools request rather than the affected call.
|
||||
const uncompilable = patterns.flatMap((entry) => {
|
||||
try {
|
||||
new RegExp(entry.pattern, "v");
|
||||
return [];
|
||||
} catch (cause: unknown) {
|
||||
return [
|
||||
`${entry.tool} ${entry.where} ${entry.pattern}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
];
|
||||
}
|
||||
});
|
||||
const uncompilable = patterns.flatMap((entry) =>
|
||||
["", "u", "v"].flatMap((flags) => {
|
||||
try {
|
||||
new RegExp(entry.pattern, flags);
|
||||
return [];
|
||||
} catch (cause: unknown) {
|
||||
return [
|
||||
`${entry.tool} ${entry.where} /${entry.pattern}/${flags}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
];
|
||||
}
|
||||
}),
|
||||
);
|
||||
expect(uncompilable).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,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue