fix(housekeeping): harden readonly schema facade

This commit is contained in:
Simo committed 2026-08-28 20:58:14 +02:00
1 parent 139e8cfc3a
commit 2970dff563
3 files changed
+782 -15

No files matched your search

@@ -71,6 +71,45 @@ function attemptMutation(mutate: () => void): void {
}
}
function mutationWasRejected(mutate: () => void): boolean {
try {
mutate();
return false;
} catch {
return true;
}
}
const unsafeSchemaCallbackMessage =
"callback-bearing schema arguments are not supported";
const callbackOptionMethodNames = [
"parse",
"safeParse",
"parseAsync",
"safeParseAsync",
"spa",
"encode",
"decode",
"encodeAsync",
"decodeAsync",
"safeEncode",
"safeDecode",
"safeEncodeAsync",
"safeDecodeAsync",
] as const;
function attemptMinimumMutation(instance: unknown): void {
if (typeof instance !== "object" || instance === null) return;
const internal = Reflect.get(instance, "_zod") as
| { def?: { minimum?: number } }
| undefined;
if (typeof internal?.def?.minimum !== "number") return;
attemptMutation(() => {
if (internal.def) internal.def.minimum = 0;
});
}
describe("housekeeping command registry", () => {
it("returns the validated snapshot registered under its global ID", () => {
const registered = command("people.registry.lookup");
@@ -506,6 +545,337 @@ describe("housekeeping command registry", () => {
true,
);
});
it.each(callbackOptionMethodNames)(
"rejects callback-bearing %s options before private issue nodes are exposed",
async (methodName) => {
const input = z.object({ value: z.string().min(3) });
const id = `people.registry.callback-${methodName.toLowerCase()}`;
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedInstance: unknown;
const errorCallback = (issue: unknown): string => {
callbackCalls += 1;
capturedInstance = Reflect.get(issue as object, "inst");
attemptMinimumMutation(capturedInstance);
return "forged validation error";
};
const method = Reflect.get(registered.input, methodName);
if (typeof method !== "function") {
throw new Error(`public schema method missing: ${methodName}`);
}
const attack = Promise.resolve().then(() =>
Reflect.apply(method, registered.input, [
{ value: "x" },
{ error: errorCallback },
]),
);
await expect(attack).rejects.toThrow(unsafeSchemaCallbackMessage);
expect(callbackCalls).toBe(0);
expect(capturedInstance).toBeUndefined();
expect(registered.input.safeParse({ value: "x" }).success).toBe(false);
expect(registered.input.safeParse({ value: "valid" }).success).toBe(true);
},
);
it("rejects JSON-schema overrides before private schema or check nodes are exposed", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-json-schema";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedSchema: unknown;
let attackError: unknown;
try {
registered.input.toJSONSchema({
override: ({ zodSchema }) => {
callbackCalls += 1;
capturedSchema = zodSchema;
const checks = (zodSchema._zod.def as { checks?: unknown[] }).checks;
if (checks?.[0]) attemptMinimumMutation(checks[0]);
},
});
} catch (error) {
attackError = error;
}
expect({
attackError:
attackError instanceof TypeError ? attackError.message : undefined,
callbackCalls,
capturedSchema,
shortValueAccepted: registered.input.safeParse({ value: "x" }).success,
validValueAccepted: registered.input.safeParse({ value: "valid" })
.success,
}).toEqual({
attackError: unsafeSchemaCallbackMessage,
callbackCalls: 0,
capturedSchema: undefined,
shortValueAccepted: false,
validValueAccepted: true,
});
});
it("rejects an error callback concealed behind an option Proxy", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-proxy-options";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedInstance: unknown;
const errorCallback = (issue: unknown): string => {
callbackCalls += 1;
capturedInstance = Reflect.get(issue as object, "inst");
attemptMinimumMutation(capturedInstance);
return "forged validation error";
};
const concealedOptions = new Proxy(
{},
{
get: (_target, property) =>
property === "error" ? errorCallback : undefined,
getOwnPropertyDescriptor: () => undefined,
ownKeys: () => [],
},
);
let attackError: unknown;
try {
registered.input.safeParse({ value: "x" }, concealedOptions as never);
} catch (error) {
attackError = error;
}
expect({
attackError:
attackError instanceof TypeError ? attackError.message : undefined,
callbackCalls,
capturedInstance,
shortValueAccepted: registered.input.safeParse({ value: "x" }).success,
validValueAccepted: registered.input.safeParse({ value: "valid" })
.success,
}).toEqual({
attackError: unsafeSchemaCallbackMessage,
callbackCalls: 0,
capturedInstance: undefined,
shortValueAccepted: false,
validValueAccepted: true,
});
});
it("keeps callback-free sync and async parsing available", async () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-free-parse";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
expect(
registered.input.parse({ value: "valid" }, { jitless: true }),
).toEqual({ value: "valid" });
expect(
registered.input.safeParse({ value: "valid" }, { reportInput: true }),
).toMatchObject({ success: true, data: { value: "valid" } });
await expect(
registered.input.parseAsync({ value: "valid" }, { jitless: true }),
).resolves.toEqual({ value: "valid" });
await expect(
registered.input.safeParseAsync({ value: "x" }, { reportInput: true }),
).resolves.toMatchObject({ success: false });
});
it("exposes detached Map, Set, and Date values as behaviorally readonly views", () => {
const sourceMap = new Map<string, { count: number }>([
["registered", { count: 1 }],
]);
const sourceSet = new Set(["registered"]);
const sourceDate = new Date("2026-08-26T12:34:56.000Z");
const input = z.object({ value: z.string() });
Object.assign(input.def, {
exposedMap: sourceMap,
exposedSet: sourceSet,
exposedDate: sourceDate,
});
const id = "people.registry.readonly-builtins";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicDefinition = registered.input
.def as typeof registered.input.def & {
exposedMap: ReadonlyMap<string, { count: number }>;
exposedSet: ReadonlySet<string>;
exposedDate: Date;
};
const publicMap = publicDefinition.exposedMap;
const publicSet = publicDefinition.exposedSet;
const publicDate = publicDefinition.exposedDate;
sourceMap.set("caller-late", { count: 2 });
sourceSet.add("caller-late");
sourceDate.setTime(0);
const mapForEachReceivers: unknown[] = [];
const mapEntries: Array<[string, number]> = [];
publicMap.forEach((value, key, collection) => {
mapEntries.push([key, value.count]);
mapForEachReceivers.push(collection);
});
const setForEachReceivers: unknown[] = [];
const setEntries: string[] = [];
publicSet.forEach((value, duplicate, collection) => {
expect(duplicate).toBe(value);
setEntries.push(value);
setForEachReceivers.push(collection);
});
const dateIsoBefore = publicDate.toISOString();
const mapEscape = Symbol("map-escape");
const setEscape = Symbol("set-escape");
const dateEscape = Symbol("date-escape");
Object.defineProperty(Map.prototype, mapEscape, {
configurable: true,
value(this: Map<unknown, unknown>): Map<unknown, unknown> {
return this;
},
});
Object.defineProperty(Set.prototype, setEscape, {
configurable: true,
value(this: Set<unknown>): Set<unknown> {
return this;
},
});
Object.defineProperty(Date.prototype, dateEscape, {
configurable: true,
value(this: Date): Date {
return this;
},
});
let escapedMap: unknown;
let escapedSet: unknown;
let escapedDate: unknown;
try {
const mapMethod = Reflect.get(publicMap, mapEscape);
const setMethod = Reflect.get(publicSet, setEscape);
const dateMethod = Reflect.get(publicDate, dateEscape);
if (
typeof mapMethod !== "function" ||
typeof setMethod !== "function" ||
typeof dateMethod !== "function"
) {
throw new Error("dynamic builtin method missing");
}
escapedMap = Reflect.apply(mapMethod, publicMap, []);
escapedSet = Reflect.apply(setMethod, publicSet, []);
escapedDate = Reflect.apply(dateMethod, publicDate, []);
} finally {
Reflect.deleteProperty(Map.prototype, mapEscape);
Reflect.deleteProperty(Set.prototype, setEscape);
Reflect.deleteProperty(Date.prototype, dateEscape);
}
const escapeMutationResults = [
mutationWasRejected(() =>
(escapedMap as Map<string, { count: number }>).set("escaped-forged", {
count: 11,
}),
),
mutationWasRejected(() =>
(escapedSet as Set<string>).add("escaped-forged"),
),
mutationWasRejected(() => (escapedDate as Date).setTime(0)),
];
const mapMutationResults = [
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).set("forged", {
count: 9,
}),
),
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).delete("registered"),
),
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).clear(),
),
mutationWasRejected(() =>
Map.prototype.set.call(
publicMap as Map<string, { count: number }>,
"prototype-forged",
{ count: 10 },
),
),
];
const setMutationResults = [
mutationWasRejected(() => (publicSet as Set<string>).add("forged")),
mutationWasRejected(() =>
(publicSet as Set<string>).delete("registered"),
),
mutationWasRejected(() => (publicSet as Set<string>).clear()),
mutationWasRejected(() =>
Set.prototype.add.call(publicSet as Set<string>, "prototype-forged"),
),
];
const dateMutationResults = Object.getOwnPropertyNames(Date.prototype)
.filter((property) => property.startsWith("set"))
.map((property) =>
mutationWasRejected(() => {
const method = Reflect.get(publicDate, property);
if (typeof method !== "function") {
throw new TypeError(`date mutator unavailable: ${property}`);
}
Reflect.apply(method, publicDate, [0]);
}),
);
dateMutationResults.push(
mutationWasRejected(() => Date.prototype.setTime.call(publicDate, 0)),
);
let dateIsoAfter: string | undefined;
try {
dateIsoAfter = publicDate.toISOString();
} catch {
dateIsoAfter = undefined;
}
expect(dateMutationResults.length).toBeGreaterThan(1);
expect(dateMutationResults.every(Boolean)).toBe(true);
expect(escapeMutationResults).toEqual([true, true, true]);
expect(mapEntries).toEqual([["registered", 1]]);
expect(mapForEachReceivers).toHaveLength(1);
expect(mapForEachReceivers[0]).toBe(publicMap);
expect(mapMutationResults).toEqual([true, true, true, true]);
expect([...publicMap.entries()]).toEqual([["registered", { count: 1 }]]);
expect(setEntries).toEqual(["registered"]);
expect(setForEachReceivers).toHaveLength(1);
expect(setForEachReceivers[0]).toBe(publicSet);
expect(setMutationResults).toEqual([true, true, true, true]);
expect([...publicSet]).toEqual(["registered"]);
expect(dateIsoBefore).toBe("2026-08-26T12:34:56.000Z");
expect(dateIsoAfter).toBe("2026-08-26T12:34:56.000Z");
});
it("seals the global registry after deterministic bootstrap", () => {
sealHousekeepingCommandRegistry();