@apifuse/provider-sdk 2.2.0-beta.24 → 2.2.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +7 -6
- package/CHANGELOG.md +9 -1
- package/README.md +3 -3
- package/bin/apifuse-check.ts +62 -3
- package/bin/apifuse-pack-check.ts +8 -2
- package/bin/apifuse-pack-smoke.ts +43 -2
- package/bin/apifuse-pack-types.ts +58 -0
- package/bin/apifuse-submit-check.ts +15 -2
- package/dist/auth.js +29 -0
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/contract-serialization.d.ts +20 -1
- package/dist/contract-serialization.js +583 -8
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +9 -5
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +1 -1
- package/dist/define.js +13 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -3
- package/dist/lint.js +85 -3
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/resolver-vendors/bindings.d.ts +42 -2
- package/dist/runtime/resolver-vendors/bindings.js +31 -6
- package/dist/runtime/resolver-vendors/browser.d.ts +3 -7
- package/dist/runtime/resolver-vendors/browser.js +7 -22
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +23 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +264 -0
- package/dist/runtime/resolver-vendors/types.d.ts +44 -3
- package/dist/runtime/resolver-vendors/types.js +10 -0
- package/dist/runtime/resolver.d.ts +17 -2
- package/dist/runtime/resolver.js +237 -15
- package/dist/runtime/stealth.d.ts +26 -4
- package/dist/runtime/stealth.js +224 -114
- package/dist/schema.d.ts +63 -0
- package/dist/schema.js +808 -8
- package/dist/server/serve.js +8 -0
- package/dist/stealth/profiles.js +16 -7
- package/dist/types.d.ts +37 -4
- package/package.json +2 -2
- package/src/auth.ts +40 -0
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/contract-serialization.ts +857 -8
- package/src/contract.ts +16 -5
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +23 -2
- package/src/index.ts +13 -0
- package/src/lint.ts +98 -3
- package/src/provider.ts +10 -0
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/resolver-vendors/bindings.ts +40 -15
- package/src/runtime/resolver-vendors/browser.ts +9 -31
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +366 -0
- package/src/runtime/resolver-vendors/types.ts +54 -0
- package/src/runtime/resolver.ts +304 -24
- package/src/runtime/stealth.ts +317 -136
- package/src/schema.ts +1060 -9
- package/src/server/serve.ts +8 -0
- package/src/stealth/profiles.ts +17 -7
- package/src/types.ts +39 -6
package/src/schema.ts
CHANGED
|
@@ -83,8 +83,42 @@ export function safeParseSchemaSync(
|
|
|
83
83
|
export const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
|
|
84
84
|
export const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
|
|
85
85
|
export const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
|
|
86
|
+
export const APIFUSE_TEXT_TRUST_META_KEY = "x-apifuse-text-trust";
|
|
86
87
|
export const APIFUSE_REDACTION_MARKER = "<redacted>";
|
|
87
88
|
|
|
89
|
+
export type TextTrust = "trusted" | "untrusted";
|
|
90
|
+
|
|
91
|
+
export interface TextTrustMetadata {
|
|
92
|
+
readonly v: 1;
|
|
93
|
+
readonly trust: TextTrust;
|
|
94
|
+
readonly carrier?: "container";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type OutputTextTrustMap = Readonly<Record<string, TextTrust>>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Auto-trusted output leaves are string-valued `z.literal()` / `z.enum()`,
|
|
101
|
+
* patterns whose every top-level alternative is anchored at both ends and
|
|
102
|
+
* whose bodies use only finite quantifiers, have a maximum match length of 32,
|
|
103
|
+
* and contain only digits and safe punctuation in positive whitespace-free
|
|
104
|
+
* character classes, literals/escapes, and ordinary or non-capturing groups,
|
|
105
|
+
* plus the structurally constrained formats listed here. A brand is trusted
|
|
106
|
+
* only when its underlying schema meets one of those rules. Letter-bearing
|
|
107
|
+
* identifiers, length-only strings, and other formats (including email, URL,
|
|
108
|
+
* base64, and JWT) are not trusted.
|
|
109
|
+
*/
|
|
110
|
+
export const AUTO_TRUSTED_ZOD_STRING_FORMATS = [
|
|
111
|
+
"cidrv4",
|
|
112
|
+
"cidrv6",
|
|
113
|
+
"date",
|
|
114
|
+
"datetime",
|
|
115
|
+
"duration",
|
|
116
|
+
"e164",
|
|
117
|
+
"ipv4",
|
|
118
|
+
"ipv6",
|
|
119
|
+
"time",
|
|
120
|
+
] as const;
|
|
121
|
+
|
|
88
122
|
export type SensitivePathSegment = string | "*";
|
|
89
123
|
export type SensitivePath = readonly SensitivePathSegment[];
|
|
90
124
|
export type SensitiveFieldKind =
|
|
@@ -127,9 +161,19 @@ export function describeKey<TSchema extends ZodType>(
|
|
|
127
161
|
});
|
|
128
162
|
}
|
|
129
163
|
|
|
164
|
+
/** Attach output text-trust authoring metadata without changing validation. */
|
|
165
|
+
export function textTrust<TSchema extends ZodType>(schema: TSchema, trust: TextTrust): TSchema {
|
|
166
|
+
const metadata = schema.meta() ?? {};
|
|
167
|
+
return schema.meta({
|
|
168
|
+
...metadata,
|
|
169
|
+
[APIFUSE_TEXT_TRUST_META_KEY]: { v: 1, trust } satisfies TextTrustMetadata,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
130
173
|
declare module "zod" {
|
|
131
174
|
interface ZodType {
|
|
132
175
|
describeKey(key: ProviderLocaleKey | string): this;
|
|
176
|
+
textTrust(trust: TextTrust): this;
|
|
133
177
|
}
|
|
134
178
|
}
|
|
135
179
|
|
|
@@ -140,16 +184,101 @@ const describeKeyMethod = function <TSchema extends ZodType>(
|
|
|
140
184
|
return describeKey(this, key);
|
|
141
185
|
};
|
|
142
186
|
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
187
|
+
const textTrustMethod = function <TSchema extends ZodType>(
|
|
188
|
+
this: TSchema,
|
|
189
|
+
trust: TextTrust,
|
|
190
|
+
): TSchema {
|
|
191
|
+
return textTrust(this, trust);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
interface StaticOutputFallback {
|
|
195
|
+
readonly value: unknown;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const staticOutputFallbacks = new WeakMap<object, StaticOutputFallback>();
|
|
199
|
+
let suppressDynamicOutputFallbacks = 0;
|
|
200
|
+
|
|
201
|
+
function trackOutputFallback(schema: unknown, fallback: unknown): void {
|
|
202
|
+
if (!(schema instanceof z.ZodType)) return;
|
|
203
|
+
const def = schema._zod.def;
|
|
204
|
+
if (typeof fallback !== "function") {
|
|
205
|
+
staticOutputFallbacks.set(def, { value: fallback });
|
|
146
206
|
return;
|
|
147
207
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
208
|
+
if (def.type === "default") {
|
|
209
|
+
const descriptor = Object.getOwnPropertyDescriptor(def, "defaultValue");
|
|
210
|
+
if (!descriptor?.get) return;
|
|
211
|
+
const originalGetter = descriptor.get;
|
|
212
|
+
Object.defineProperty(def, "defaultValue", {
|
|
213
|
+
...descriptor,
|
|
214
|
+
get(this: object) {
|
|
215
|
+
if (suppressDynamicOutputFallbacks > 0) return null;
|
|
216
|
+
return Reflect.apply(originalGetter, this, []);
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (def.type === "catch") {
|
|
222
|
+
const catchValue = Reflect.get(def, "catchValue");
|
|
223
|
+
if (typeof catchValue !== "function") return;
|
|
224
|
+
Reflect.set(def, "catchValue", function (this: unknown, ...args: unknown[]) {
|
|
225
|
+
if (suppressDynamicOutputFallbacks > 0) return null as never;
|
|
226
|
+
return Reflect.apply(catchValue, this, args) as never;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const trackedFallbackPrototypes = new WeakSet<object>();
|
|
232
|
+
|
|
233
|
+
function installOutputFallbackTrackingOnPrototype(prototype: unknown): void {
|
|
234
|
+
if (!prototype || typeof prototype !== "object" || trackedFallbackPrototypes.has(prototype)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
trackedFallbackPrototypes.add(prototype);
|
|
238
|
+
Reflect.apply(z.ZodType.init, z.ZodType, [Object.create(prototype), { type: "custom" }]);
|
|
239
|
+
for (const methodName of ["default", "catch"] as const) {
|
|
240
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
|
|
241
|
+
if (!descriptor?.configurable || !descriptor.get) continue;
|
|
242
|
+
const originalGetter = descriptor.get;
|
|
243
|
+
Object.defineProperty(prototype, methodName, {
|
|
244
|
+
...descriptor,
|
|
245
|
+
get(this: object) {
|
|
246
|
+
const originalMethod = Reflect.apply(originalGetter, this, []);
|
|
247
|
+
if (typeof originalMethod !== "function") return originalMethod;
|
|
248
|
+
const trackedMethod = function (this: unknown, fallback: unknown) {
|
|
249
|
+
const result = Reflect.apply(originalMethod, this, [fallback]);
|
|
250
|
+
trackOutputFallback(result, fallback);
|
|
251
|
+
return result;
|
|
252
|
+
};
|
|
253
|
+
Object.defineProperty(this, methodName, {
|
|
254
|
+
configurable: true,
|
|
255
|
+
enumerable: true,
|
|
256
|
+
value: trackedMethod,
|
|
257
|
+
writable: true,
|
|
258
|
+
});
|
|
259
|
+
return trackedMethod;
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function installSchemaMetadataMethodsOnPrototype(prototype: unknown): void {
|
|
266
|
+
const target = prototype as Record<string, unknown> | null;
|
|
267
|
+
if (!target) return;
|
|
268
|
+
if (typeof target.describeKey !== "function") {
|
|
269
|
+
Object.defineProperty(target, "describeKey", {
|
|
270
|
+
configurable: true,
|
|
271
|
+
value: describeKeyMethod,
|
|
272
|
+
writable: true,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (typeof target.textTrust !== "function") {
|
|
276
|
+
Object.defineProperty(target, "textTrust", {
|
|
277
|
+
configurable: true,
|
|
278
|
+
value: textTrustMethod,
|
|
279
|
+
writable: true,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
153
282
|
}
|
|
154
283
|
|
|
155
284
|
for (const [name, value] of Object.entries(z)) {
|
|
@@ -159,7 +288,8 @@ for (const [name, value] of Object.entries(z)) {
|
|
|
159
288
|
if (typeof value !== "function") {
|
|
160
289
|
continue;
|
|
161
290
|
}
|
|
162
|
-
|
|
291
|
+
installSchemaMetadataMethodsOnPrototype(value.prototype);
|
|
292
|
+
installOutputFallbackTrackingOnPrototype(value.prototype);
|
|
163
293
|
}
|
|
164
294
|
|
|
165
295
|
const RESERVED_SENSITIVE_KEYS = new Set([
|
|
@@ -238,6 +368,927 @@ export const fields = {
|
|
|
238
368
|
sensitiveString("token", "Provider access or refresh token.", options),
|
|
239
369
|
} as const;
|
|
240
370
|
|
|
371
|
+
type TextTrustDeclaration = TextTrust | "absent" | "invalid";
|
|
372
|
+
|
|
373
|
+
type InternalZodSchema = z.core.$ZodTypes;
|
|
374
|
+
type InternalZodDef = InternalZodSchema["_zod"]["def"];
|
|
375
|
+
|
|
376
|
+
interface OutputTextLeaf {
|
|
377
|
+
readonly classification: TextTrust;
|
|
378
|
+
readonly classified: boolean;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
interface OutputTextTrustCollection {
|
|
382
|
+
readonly leaves: Array<OutputTextLeaf & { readonly path: string }>;
|
|
383
|
+
readonly debtPaths: Set<string>;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export class OutputTextTrustSchemaError extends TypeError {
|
|
387
|
+
readonly code = "invalid_output_text_trust_schema";
|
|
388
|
+
|
|
389
|
+
constructor(value: unknown) {
|
|
390
|
+
const receivedType = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
391
|
+
super(`Output text-trust collection requires a Zod schema; received ${receivedType}.`);
|
|
392
|
+
this.name = "OutputTextTrustSchemaError";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export class OutputTextTrustCollectionError extends Error {
|
|
397
|
+
readonly classification = "untrusted" as const;
|
|
398
|
+
readonly code = "output_text_trust_collection_failed";
|
|
399
|
+
|
|
400
|
+
constructor(
|
|
401
|
+
public readonly schemaPath: string,
|
|
402
|
+
cause: unknown,
|
|
403
|
+
) {
|
|
404
|
+
super(`Output text-trust collection failed at schema path ${schemaPath}.`);
|
|
405
|
+
this.name = "OutputTextTrustCollectionError";
|
|
406
|
+
this.cause = cause;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Collect every textual leaf in a Zod output schema as a deterministic
|
|
412
|
+
* schema-path-to-classification object. The path grammar is rooted at `$`:
|
|
413
|
+
* object keys are `["key"]`, arrays and record values are `[*]`, tuple slots
|
|
414
|
+
* are `[n]`, union/intersection alternatives are `<union:n>` / `<intersection:n>`,
|
|
415
|
+
* record keys are `<record-key>`, and one bounded cycle expansion is marked
|
|
416
|
+
* `<recursive>`.
|
|
417
|
+
*/
|
|
418
|
+
export function collectOutputTextTrust(schema: ZodType): OutputTextTrustMap {
|
|
419
|
+
const { leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
|
|
420
|
+
return Object.fromEntries(
|
|
421
|
+
leaves
|
|
422
|
+
.map(({ path, classification }) => [path, classification] as const)
|
|
423
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Report textual output paths that lack a valid explicit or auto-derived classification. */
|
|
428
|
+
export function findUnclassifiedOutputTextPaths(schema: ZodType): string[] {
|
|
429
|
+
const { debtPaths, leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
|
|
430
|
+
for (const { classified, path } of leaves) {
|
|
431
|
+
if (!classified) debtPaths.add(path);
|
|
432
|
+
}
|
|
433
|
+
return [...debtPaths].sort((left, right) => left.localeCompare(right));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** @internal Used by contract JSON Schema projection. */
|
|
437
|
+
export function resolveOutputTextTrust(
|
|
438
|
+
schema: z.core.$ZodType,
|
|
439
|
+
inheritedUntrusted = false,
|
|
440
|
+
): TextTrust | undefined {
|
|
441
|
+
const resolved = resolveOutputTextTrustProjection(schema);
|
|
442
|
+
return resolved?.[inheritedUntrusted ? "inherited" : "local"];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** @internal Resolve both projection states from one stable schema traversal. */
|
|
446
|
+
export function resolveOutputTextTrustProjection(
|
|
447
|
+
schema: z.core.$ZodType,
|
|
448
|
+
): { readonly inherited: TextTrust; readonly local: TextTrust } | undefined {
|
|
449
|
+
const resolved = resolveOutputTextLeafPair(asInternalZodSchema(schema), [], true);
|
|
450
|
+
return resolved
|
|
451
|
+
? {
|
|
452
|
+
inherited: resolved.inherited.classification,
|
|
453
|
+
local: resolved.local.classification,
|
|
454
|
+
}
|
|
455
|
+
: undefined;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** @internal Whether this node can bypass or mutate descendant validation guarantees. */
|
|
459
|
+
export function invalidatesDescendantOutputTextAutoTrust(schema: z.core.$ZodType): boolean {
|
|
460
|
+
return invalidatesOutputTextAutoTrust(asInternalZodSchema(schema)._zod.def);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** @internal Run output projection without evaluating dynamic fallback callbacks. */
|
|
464
|
+
export function suppressDynamicOutputFallbacksDuring<T>(project: () => T): T {
|
|
465
|
+
suppressDynamicOutputFallbacks += 1;
|
|
466
|
+
try {
|
|
467
|
+
return project();
|
|
468
|
+
} finally {
|
|
469
|
+
suppressDynamicOutputFallbacks -= 1;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** @internal Whether this mutator has a static fallback that cannot contain text. */
|
|
474
|
+
export function mutatorReturnIsProvablyNonText(schema: z.core.$ZodType): boolean {
|
|
475
|
+
const def = asInternalZodSchema(schema)._zod.def;
|
|
476
|
+
if (def.type !== "default" && def.type !== "catch") return false;
|
|
477
|
+
const fallback = staticOutputFallbacks.get(def);
|
|
478
|
+
return fallback !== undefined && outputFallbackValueIsProvablyNonText(fallback.value);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** @internal Whether this mutator has a static array fallback. */
|
|
482
|
+
export function mutatorUsesStaticArrayFallback(schema: z.core.$ZodType): boolean {
|
|
483
|
+
const def = asInternalZodSchema(schema)._zod.def;
|
|
484
|
+
if (def.type !== "default" && def.type !== "catch") return false;
|
|
485
|
+
const fallback = staticOutputFallbacks.get(def);
|
|
486
|
+
return fallback !== undefined && Array.isArray(fallback.value);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** @internal Whether this default or catch was authored with a callback. */
|
|
490
|
+
export function hasDynamicOutputFallback(schema: z.core.$ZodType): boolean {
|
|
491
|
+
const def = asInternalZodSchema(schema)._zod.def;
|
|
492
|
+
return (def.type === "default" || def.type === "catch") && !staticOutputFallbacks.has(def);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** @internal Used by contract JSON Schema projection. */
|
|
496
|
+
export function inheritsUntrustedOutputTextTrust(schema: z.core.$ZodType): boolean {
|
|
497
|
+
let current = asInternalZodSchema(schema);
|
|
498
|
+
while (true) {
|
|
499
|
+
if (readTextTrustDeclaration(current) === "untrusted") return true;
|
|
500
|
+
const inner = flattenedOutputSchema(current._zod.def);
|
|
501
|
+
if (!inner) return false;
|
|
502
|
+
current = inner;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function outputFallbackValueIsProvablyNonText(value: unknown): boolean {
|
|
507
|
+
return (
|
|
508
|
+
value === null ||
|
|
509
|
+
typeof value === "boolean" ||
|
|
510
|
+
typeof value === "number" ||
|
|
511
|
+
(Array.isArray(value) && value.every(outputFallbackValueIsProvablyNonText))
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function collectOutputTextLeaves(schema: InternalZodSchema): OutputTextTrustCollection {
|
|
516
|
+
const leaves: Array<OutputTextLeaf & { readonly path: string }> = [];
|
|
517
|
+
const debtPaths = new Set<string>();
|
|
518
|
+
const emittedPaths = new Set<string>();
|
|
519
|
+
walkOutputSchema(schema, "$", leaves, debtPaths, emittedPaths, new Set(), false, false, true);
|
|
520
|
+
return { leaves, debtPaths };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function walkOutputSchema(
|
|
524
|
+
schema: InternalZodSchema,
|
|
525
|
+
path: string,
|
|
526
|
+
out: Array<OutputTextLeaf & { readonly path: string }>,
|
|
527
|
+
debtPaths: Set<string>,
|
|
528
|
+
emittedPaths: Set<string>,
|
|
529
|
+
activeSchemas: Set<InternalZodSchema>,
|
|
530
|
+
expandingCycle: boolean,
|
|
531
|
+
inheritedUntrusted: boolean,
|
|
532
|
+
autoTrustAllowed: boolean,
|
|
533
|
+
): void {
|
|
534
|
+
try {
|
|
535
|
+
walkOutputSchemaUnchecked(
|
|
536
|
+
schema,
|
|
537
|
+
path,
|
|
538
|
+
out,
|
|
539
|
+
debtPaths,
|
|
540
|
+
emittedPaths,
|
|
541
|
+
activeSchemas,
|
|
542
|
+
expandingCycle,
|
|
543
|
+
inheritedUntrusted,
|
|
544
|
+
autoTrustAllowed,
|
|
545
|
+
);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (error instanceof OutputTextTrustCollectionError) throw error;
|
|
548
|
+
throw new OutputTextTrustCollectionError(path, error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function walkOutputSchemaUnchecked(
|
|
553
|
+
schema: InternalZodSchema,
|
|
554
|
+
path: string,
|
|
555
|
+
out: Array<OutputTextLeaf & { readonly path: string }>,
|
|
556
|
+
debtPaths: Set<string>,
|
|
557
|
+
emittedPaths: Set<string>,
|
|
558
|
+
activeSchemas: Set<InternalZodSchema>,
|
|
559
|
+
expandingCycle: boolean,
|
|
560
|
+
inheritedUntrusted: boolean,
|
|
561
|
+
autoTrustAllowed: boolean,
|
|
562
|
+
): void {
|
|
563
|
+
const alreadyActive = activeSchemas.has(schema);
|
|
564
|
+
if (alreadyActive) {
|
|
565
|
+
if (!expandingCycle) {
|
|
566
|
+
walkOutputSchema(
|
|
567
|
+
schema,
|
|
568
|
+
`${path}<recursive>`,
|
|
569
|
+
out,
|
|
570
|
+
debtPaths,
|
|
571
|
+
emittedPaths,
|
|
572
|
+
new Set(),
|
|
573
|
+
true,
|
|
574
|
+
inheritedUntrusted,
|
|
575
|
+
autoTrustAllowed,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const leaf = resolveOutputTextLeaf(schema, [], autoTrustAllowed, inheritedUntrusted);
|
|
582
|
+
if (leaf) {
|
|
583
|
+
if (!emittedPaths.has(path)) {
|
|
584
|
+
emittedPaths.add(path);
|
|
585
|
+
out.push({ path, ...leaf });
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const def = schema._zod.def;
|
|
591
|
+
const declaration = readTextTrustDeclaration(schema);
|
|
592
|
+
if (declaration === "trusted" || declaration === "invalid") debtPaths.add(path);
|
|
593
|
+
const descendantUntrusted = inheritedUntrusted || declaration === "untrusted";
|
|
594
|
+
const descendantAutoTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
|
|
595
|
+
if (
|
|
596
|
+
invalidatesOutputTextAutoTrust(def) &&
|
|
597
|
+
!mutatorReturnIsProvablyNonText(schema) &&
|
|
598
|
+
!mutatorCanUseCollectedTextItems(schema) &&
|
|
599
|
+
!emittedPaths.has(path)
|
|
600
|
+
) {
|
|
601
|
+
emittedPaths.add(path);
|
|
602
|
+
out.push({
|
|
603
|
+
classification: "untrusted",
|
|
604
|
+
classified: descendantUntrusted,
|
|
605
|
+
path,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
if (!alreadyActive) activeSchemas.add(schema);
|
|
609
|
+
try {
|
|
610
|
+
switch (def.type) {
|
|
611
|
+
case "object": {
|
|
612
|
+
for (const [key, child] of Object.entries(def.shape).sort(([left], [right]) =>
|
|
613
|
+
left.localeCompare(right),
|
|
614
|
+
)) {
|
|
615
|
+
walkOutputSchema(
|
|
616
|
+
asInternalZodSchema(child),
|
|
617
|
+
`${path}[${JSON.stringify(key)}]`,
|
|
618
|
+
out,
|
|
619
|
+
debtPaths,
|
|
620
|
+
emittedPaths,
|
|
621
|
+
activeSchemas,
|
|
622
|
+
expandingCycle,
|
|
623
|
+
descendantUntrusted,
|
|
624
|
+
descendantAutoTrustAllowed,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
if (def.catchall) {
|
|
628
|
+
if (asInternalZodSchema(def.catchall)._zod.def.type !== "never") {
|
|
629
|
+
const catchallKeyPath = `${path}<catchall-key>`;
|
|
630
|
+
if (!emittedPaths.has(catchallKeyPath)) {
|
|
631
|
+
emittedPaths.add(catchallKeyPath);
|
|
632
|
+
out.push({
|
|
633
|
+
classification: "untrusted",
|
|
634
|
+
classified: descendantUntrusted,
|
|
635
|
+
path: catchallKeyPath,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
walkOutputSchema(
|
|
640
|
+
asInternalZodSchema(def.catchall),
|
|
641
|
+
`${path}[*]`,
|
|
642
|
+
out,
|
|
643
|
+
debtPaths,
|
|
644
|
+
emittedPaths,
|
|
645
|
+
activeSchemas,
|
|
646
|
+
expandingCycle,
|
|
647
|
+
descendantUntrusted,
|
|
648
|
+
descendantAutoTrustAllowed,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
case "array":
|
|
654
|
+
walkOutputSchema(
|
|
655
|
+
asInternalZodSchema(def.element),
|
|
656
|
+
`${path}[*]`,
|
|
657
|
+
out,
|
|
658
|
+
debtPaths,
|
|
659
|
+
emittedPaths,
|
|
660
|
+
activeSchemas,
|
|
661
|
+
expandingCycle,
|
|
662
|
+
descendantUntrusted,
|
|
663
|
+
descendantAutoTrustAllowed,
|
|
664
|
+
);
|
|
665
|
+
break;
|
|
666
|
+
case "tuple": {
|
|
667
|
+
def.items.forEach((child, index) => {
|
|
668
|
+
walkOutputSchema(
|
|
669
|
+
asInternalZodSchema(child),
|
|
670
|
+
`${path}[${index}]`,
|
|
671
|
+
out,
|
|
672
|
+
debtPaths,
|
|
673
|
+
emittedPaths,
|
|
674
|
+
activeSchemas,
|
|
675
|
+
expandingCycle,
|
|
676
|
+
descendantUntrusted,
|
|
677
|
+
descendantAutoTrustAllowed,
|
|
678
|
+
);
|
|
679
|
+
});
|
|
680
|
+
if (def.rest) {
|
|
681
|
+
walkOutputSchema(
|
|
682
|
+
asInternalZodSchema(def.rest),
|
|
683
|
+
`${path}[*]`,
|
|
684
|
+
out,
|
|
685
|
+
debtPaths,
|
|
686
|
+
emittedPaths,
|
|
687
|
+
activeSchemas,
|
|
688
|
+
expandingCycle,
|
|
689
|
+
descendantUntrusted,
|
|
690
|
+
descendantAutoTrustAllowed,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
case "record":
|
|
696
|
+
walkOutputSchema(
|
|
697
|
+
asInternalZodSchema(def.keyType),
|
|
698
|
+
`${path}<record-key>`,
|
|
699
|
+
out,
|
|
700
|
+
debtPaths,
|
|
701
|
+
emittedPaths,
|
|
702
|
+
activeSchemas,
|
|
703
|
+
expandingCycle,
|
|
704
|
+
descendantUntrusted,
|
|
705
|
+
descendantAutoTrustAllowed,
|
|
706
|
+
);
|
|
707
|
+
walkOutputSchema(
|
|
708
|
+
asInternalZodSchema(def.valueType),
|
|
709
|
+
`${path}[*]`,
|
|
710
|
+
out,
|
|
711
|
+
debtPaths,
|
|
712
|
+
emittedPaths,
|
|
713
|
+
activeSchemas,
|
|
714
|
+
expandingCycle,
|
|
715
|
+
descendantUntrusted,
|
|
716
|
+
descendantAutoTrustAllowed,
|
|
717
|
+
);
|
|
718
|
+
break;
|
|
719
|
+
case "union":
|
|
720
|
+
def.options.forEach((child, index) => {
|
|
721
|
+
walkOutputSchema(
|
|
722
|
+
asInternalZodSchema(child),
|
|
723
|
+
`${path}<union:${index}>`,
|
|
724
|
+
out,
|
|
725
|
+
debtPaths,
|
|
726
|
+
emittedPaths,
|
|
727
|
+
activeSchemas,
|
|
728
|
+
expandingCycle,
|
|
729
|
+
descendantUntrusted,
|
|
730
|
+
descendantAutoTrustAllowed,
|
|
731
|
+
);
|
|
732
|
+
});
|
|
733
|
+
break;
|
|
734
|
+
case "intersection":
|
|
735
|
+
for (const [index, side] of [def.left, def.right].entries()) {
|
|
736
|
+
walkOutputSchema(
|
|
737
|
+
asInternalZodSchema(side),
|
|
738
|
+
`${path}<intersection:${index}>`,
|
|
739
|
+
out,
|
|
740
|
+
debtPaths,
|
|
741
|
+
emittedPaths,
|
|
742
|
+
activeSchemas,
|
|
743
|
+
expandingCycle,
|
|
744
|
+
descendantUntrusted,
|
|
745
|
+
descendantAutoTrustAllowed,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
case "optional":
|
|
750
|
+
case "nullable":
|
|
751
|
+
case "default":
|
|
752
|
+
case "prefault":
|
|
753
|
+
case "catch":
|
|
754
|
+
case "readonly":
|
|
755
|
+
case "nonoptional":
|
|
756
|
+
case "promise":
|
|
757
|
+
walkOutputSchema(
|
|
758
|
+
asInternalZodSchema(def.innerType),
|
|
759
|
+
path,
|
|
760
|
+
out,
|
|
761
|
+
debtPaths,
|
|
762
|
+
emittedPaths,
|
|
763
|
+
activeSchemas,
|
|
764
|
+
expandingCycle,
|
|
765
|
+
descendantUntrusted,
|
|
766
|
+
descendantAutoTrustAllowed,
|
|
767
|
+
);
|
|
768
|
+
break;
|
|
769
|
+
case "pipe":
|
|
770
|
+
walkOutputSchema(
|
|
771
|
+
asInternalZodSchema(def.out),
|
|
772
|
+
path,
|
|
773
|
+
out,
|
|
774
|
+
debtPaths,
|
|
775
|
+
emittedPaths,
|
|
776
|
+
activeSchemas,
|
|
777
|
+
expandingCycle,
|
|
778
|
+
descendantUntrusted,
|
|
779
|
+
descendantAutoTrustAllowed,
|
|
780
|
+
);
|
|
781
|
+
break;
|
|
782
|
+
case "lazy":
|
|
783
|
+
walkOutputSchema(
|
|
784
|
+
asInternalZodSchema(def.getter()),
|
|
785
|
+
path,
|
|
786
|
+
out,
|
|
787
|
+
debtPaths,
|
|
788
|
+
emittedPaths,
|
|
789
|
+
activeSchemas,
|
|
790
|
+
expandingCycle,
|
|
791
|
+
descendantUntrusted,
|
|
792
|
+
descendantAutoTrustAllowed,
|
|
793
|
+
);
|
|
794
|
+
break;
|
|
795
|
+
case "string":
|
|
796
|
+
case "template_literal":
|
|
797
|
+
case "literal":
|
|
798
|
+
case "enum":
|
|
799
|
+
case "number":
|
|
800
|
+
case "bigint":
|
|
801
|
+
case "boolean":
|
|
802
|
+
case "date":
|
|
803
|
+
case "symbol":
|
|
804
|
+
case "undefined":
|
|
805
|
+
case "null":
|
|
806
|
+
case "any":
|
|
807
|
+
case "unknown":
|
|
808
|
+
case "never":
|
|
809
|
+
case "void":
|
|
810
|
+
case "map":
|
|
811
|
+
case "set":
|
|
812
|
+
case "function":
|
|
813
|
+
case "custom":
|
|
814
|
+
case "transform":
|
|
815
|
+
case "nan":
|
|
816
|
+
case "success":
|
|
817
|
+
case "file":
|
|
818
|
+
break;
|
|
819
|
+
default:
|
|
820
|
+
assertNeverZodDef(def);
|
|
821
|
+
}
|
|
822
|
+
} finally {
|
|
823
|
+
if (!alreadyActive) activeSchemas.delete(schema);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function mutatorCanUseCollectedTextItems(schema: InternalZodSchema): boolean {
|
|
828
|
+
if (!mutatorUsesStaticArrayFallback(schema)) return false;
|
|
829
|
+
const inner = flattenedOutputSchema(schema._zod.def);
|
|
830
|
+
if (inner?._zod.def.type !== "array") return false;
|
|
831
|
+
return collectOutputTextLeaves(asInternalZodSchema(inner._zod.def.element)).leaves.length > 0;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function resolveOutputTextLeaf(
|
|
835
|
+
schema: InternalZodSchema,
|
|
836
|
+
outerDeclarations: readonly TextTrustDeclaration[],
|
|
837
|
+
autoTrustAllowed: boolean,
|
|
838
|
+
inheritedUntrusted: boolean,
|
|
839
|
+
): OutputTextLeaf | undefined {
|
|
840
|
+
const resolved = resolveOutputTextLeafPair(schema, outerDeclarations, autoTrustAllowed);
|
|
841
|
+
return resolved?.[inheritedUntrusted ? "inherited" : "local"];
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function resolveOutputTextLeafPair(
|
|
845
|
+
schema: InternalZodSchema,
|
|
846
|
+
outerDeclarations: readonly TextTrustDeclaration[],
|
|
847
|
+
autoTrustAllowed: boolean,
|
|
848
|
+
): { readonly inherited: OutputTextLeaf; readonly local: OutputTextLeaf } | undefined {
|
|
849
|
+
const declarations = [...outerDeclarations, readTextTrustDeclaration(schema)];
|
|
850
|
+
const def = schema._zod.def;
|
|
851
|
+
if (!isOutputTextLeafDef(def)) {
|
|
852
|
+
const inner = flattenedOutputSchema(def);
|
|
853
|
+
return inner
|
|
854
|
+
? resolveOutputTextLeafPair(
|
|
855
|
+
inner,
|
|
856
|
+
declarations,
|
|
857
|
+
autoTrustAllowed && !invalidatesOutputTextAutoTrust(def),
|
|
858
|
+
)
|
|
859
|
+
: undefined;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const autoTrusted =
|
|
863
|
+
autoTrustAllowed && !invalidatesOutputTextAutoTrust(def) && isAutoTrustedOutputTextLeaf(schema);
|
|
864
|
+
const explicitTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
|
|
865
|
+
return {
|
|
866
|
+
inherited: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, true),
|
|
867
|
+
local: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, false),
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function classifyOutputTextLeaf(
|
|
872
|
+
declarations: readonly TextTrustDeclaration[],
|
|
873
|
+
autoTrusted: boolean,
|
|
874
|
+
explicitTrustAllowed: boolean,
|
|
875
|
+
inheritedUntrusted: boolean,
|
|
876
|
+
): OutputTextLeaf {
|
|
877
|
+
if (declarations.includes("invalid")) {
|
|
878
|
+
return { classification: "untrusted", classified: false };
|
|
879
|
+
}
|
|
880
|
+
if (declarations.includes("untrusted")) {
|
|
881
|
+
return { classification: "untrusted", classified: true };
|
|
882
|
+
}
|
|
883
|
+
if (declarations.includes("trusted")) {
|
|
884
|
+
return {
|
|
885
|
+
classification: explicitTrustAllowed ? "trusted" : "untrusted",
|
|
886
|
+
classified: explicitTrustAllowed,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
if (inheritedUntrusted) {
|
|
890
|
+
return { classification: "untrusted", classified: true };
|
|
891
|
+
}
|
|
892
|
+
return autoTrusted
|
|
893
|
+
? { classification: "trusted", classified: true }
|
|
894
|
+
: { classification: "untrusted", classified: false };
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function flattenedOutputSchema(def: InternalZodDef): InternalZodSchema | undefined {
|
|
898
|
+
switch (def.type) {
|
|
899
|
+
case "optional":
|
|
900
|
+
case "nullable":
|
|
901
|
+
case "default":
|
|
902
|
+
case "prefault":
|
|
903
|
+
case "catch":
|
|
904
|
+
case "readonly":
|
|
905
|
+
case "nonoptional":
|
|
906
|
+
case "promise":
|
|
907
|
+
return asInternalZodSchema(def.innerType);
|
|
908
|
+
case "pipe":
|
|
909
|
+
return asInternalZodSchema(def.out);
|
|
910
|
+
case "lazy":
|
|
911
|
+
return asInternalZodSchema(def.getter());
|
|
912
|
+
case "string":
|
|
913
|
+
case "template_literal":
|
|
914
|
+
case "literal":
|
|
915
|
+
case "enum":
|
|
916
|
+
case "number":
|
|
917
|
+
case "bigint":
|
|
918
|
+
case "boolean":
|
|
919
|
+
case "date":
|
|
920
|
+
case "symbol":
|
|
921
|
+
case "undefined":
|
|
922
|
+
case "null":
|
|
923
|
+
case "any":
|
|
924
|
+
case "unknown":
|
|
925
|
+
case "never":
|
|
926
|
+
case "void":
|
|
927
|
+
case "object":
|
|
928
|
+
case "array":
|
|
929
|
+
case "tuple":
|
|
930
|
+
case "record":
|
|
931
|
+
case "union":
|
|
932
|
+
case "intersection":
|
|
933
|
+
case "map":
|
|
934
|
+
case "set":
|
|
935
|
+
case "function":
|
|
936
|
+
case "custom":
|
|
937
|
+
case "transform":
|
|
938
|
+
case "nan":
|
|
939
|
+
case "success":
|
|
940
|
+
case "file":
|
|
941
|
+
return undefined;
|
|
942
|
+
default:
|
|
943
|
+
return assertNeverZodDef(def);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function isOutputTextLeafDef(def: InternalZodDef): boolean {
|
|
948
|
+
switch (def.type) {
|
|
949
|
+
case "string":
|
|
950
|
+
case "template_literal":
|
|
951
|
+
return true;
|
|
952
|
+
case "literal":
|
|
953
|
+
return def.values.some((value) => typeof value === "string");
|
|
954
|
+
case "enum":
|
|
955
|
+
return Object.values(def.entries).some((value) => typeof value === "string");
|
|
956
|
+
case "any":
|
|
957
|
+
case "unknown":
|
|
958
|
+
case "custom":
|
|
959
|
+
case "transform":
|
|
960
|
+
return true;
|
|
961
|
+
default:
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function isAutoTrustedOutputTextLeaf(schema: InternalZodSchema): boolean {
|
|
967
|
+
const def = schema._zod.def;
|
|
968
|
+
if (hasUnsafeOutputCheck(def)) return false;
|
|
969
|
+
if (def.type === "literal" || def.type === "enum") return isOutputTextLeafDef(def);
|
|
970
|
+
if (def.type !== "string" && def.type !== "template_literal") return false;
|
|
971
|
+
|
|
972
|
+
const { bag, pattern } = schema._zod;
|
|
973
|
+
if (hasSdkOwnedStringFormatValidator(schema)) {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (pattern instanceof RegExp && isRestrictiveAnchoredPattern(pattern)) return true;
|
|
978
|
+
const patterns = bag.patterns;
|
|
979
|
+
return (
|
|
980
|
+
patterns instanceof Set &&
|
|
981
|
+
patterns.size > 0 &&
|
|
982
|
+
[...patterns].every(
|
|
983
|
+
(candidate) => candidate instanceof RegExp && isRestrictiveAnchoredPattern(candidate),
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function hasUnsafeOutputCheck(def: InternalZodDef): boolean {
|
|
989
|
+
const checks = Reflect.get(def, "checks");
|
|
990
|
+
return (
|
|
991
|
+
Array.isArray(checks) &&
|
|
992
|
+
checks.some((check) => {
|
|
993
|
+
if (!check || typeof check !== "object") return false;
|
|
994
|
+
const internals = Reflect.get(check, "_zod");
|
|
995
|
+
if (!internals || typeof internals !== "object") return false;
|
|
996
|
+
const checkDef = Reflect.get(internals, "def");
|
|
997
|
+
if (!checkDef || typeof checkDef !== "object") return false;
|
|
998
|
+
const kind = Reflect.get(checkDef, "check");
|
|
999
|
+
return kind === "overwrite" || kind === "custom";
|
|
1000
|
+
})
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function invalidatesOutputTextAutoTrust(def: InternalZodDef): boolean {
|
|
1005
|
+
return (
|
|
1006
|
+
def.type === "default" ||
|
|
1007
|
+
def.type === "catch" ||
|
|
1008
|
+
def.type === "transform" ||
|
|
1009
|
+
hasUnsafeOutputCheck(def)
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
interface OwnedStringFormatValidator {
|
|
1014
|
+
readonly constructor: unknown;
|
|
1015
|
+
readonly patternFlags: string;
|
|
1016
|
+
readonly patternSource: string;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const SDK_OWNED_STRING_FORMAT_VALIDATORS: readonly OwnedStringFormatValidator[] = [
|
|
1020
|
+
z.cidrv4(),
|
|
1021
|
+
z.cidrv6(),
|
|
1022
|
+
z.iso.date(),
|
|
1023
|
+
z.iso.datetime(),
|
|
1024
|
+
z.iso.duration(),
|
|
1025
|
+
z.e164(),
|
|
1026
|
+
z.ipv4(),
|
|
1027
|
+
z.ipv6(),
|
|
1028
|
+
z.iso.time(),
|
|
1029
|
+
].map((validator) => {
|
|
1030
|
+
const pattern = Reflect.get(validator._zod.def, "pattern");
|
|
1031
|
+
if (!(pattern instanceof RegExp)) {
|
|
1032
|
+
throw new TypeError("SDK-owned Zod string format validator is missing its pattern.");
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
constructor: validator.constructor,
|
|
1036
|
+
patternFlags: pattern.flags,
|
|
1037
|
+
patternSource: pattern.source,
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
function hasSdkOwnedStringFormatValidator(schema: InternalZodSchema): boolean {
|
|
1042
|
+
const candidates: unknown[] = [schema];
|
|
1043
|
+
const checks = Reflect.get(schema._zod.def, "checks");
|
|
1044
|
+
if (Array.isArray(checks)) candidates.push(...checks);
|
|
1045
|
+
return candidates.some((candidate) => {
|
|
1046
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1047
|
+
const internals = Reflect.get(candidate, "_zod");
|
|
1048
|
+
if (!internals || typeof internals !== "object") return false;
|
|
1049
|
+
const def = Reflect.get(internals, "def");
|
|
1050
|
+
if (!def || typeof def !== "object" || Reflect.has(def, "fn")) return false;
|
|
1051
|
+
const pattern = Reflect.get(def, "pattern");
|
|
1052
|
+
if (!(pattern instanceof RegExp)) return false;
|
|
1053
|
+
return SDK_OWNED_STRING_FORMAT_VALIDATORS.some(
|
|
1054
|
+
(owned) =>
|
|
1055
|
+
Reflect.get(candidate, "constructor") === owned.constructor &&
|
|
1056
|
+
pattern.source === owned.patternSource &&
|
|
1057
|
+
pattern.flags === owned.patternFlags,
|
|
1058
|
+
);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function isRestrictiveAnchoredPattern(pattern: RegExp): boolean {
|
|
1063
|
+
if (pattern.multiline) return false;
|
|
1064
|
+
const alternatives = splitTopLevelRegexAlternatives(pattern.source);
|
|
1065
|
+
if (alternatives.length === 0) return false;
|
|
1066
|
+
let maximumLength = 0;
|
|
1067
|
+
for (const alternative of alternatives) {
|
|
1068
|
+
if (!alternative.startsWith("^") || !alternative.endsWith("$")) return false;
|
|
1069
|
+
const analysis = analyzeProvablyRestrictedRegexBody(alternative.slice(1, -1));
|
|
1070
|
+
if (!analysis) return false;
|
|
1071
|
+
maximumLength = Math.max(maximumLength, analysis.maximumLength);
|
|
1072
|
+
}
|
|
1073
|
+
return maximumLength <= MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function splitTopLevelRegexAlternatives(source: string): string[] {
|
|
1077
|
+
const alternatives: string[] = [];
|
|
1078
|
+
let start = 0;
|
|
1079
|
+
let escaped = false;
|
|
1080
|
+
let inCharacterClass = false;
|
|
1081
|
+
let groupDepth = 0;
|
|
1082
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1083
|
+
const character = source[index];
|
|
1084
|
+
if (escaped) {
|
|
1085
|
+
escaped = false;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (character === "\\") {
|
|
1089
|
+
escaped = true;
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (character === "[") {
|
|
1093
|
+
inCharacterClass = true;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (character === "]") {
|
|
1097
|
+
inCharacterClass = false;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (inCharacterClass) continue;
|
|
1101
|
+
if (character === "(") {
|
|
1102
|
+
groupDepth += 1;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (character === ")") {
|
|
1106
|
+
groupDepth -= 1;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (character === "|" && groupDepth === 0) {
|
|
1110
|
+
alternatives.push(source.slice(start, index));
|
|
1111
|
+
start = index + 1;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
alternatives.push(source.slice(start));
|
|
1115
|
+
return alternatives;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
interface RestrictedRegexAnalysis {
|
|
1119
|
+
readonly maximumLength: number;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
interface SafeRegexCharacterClassAnalysis extends RestrictedRegexAnalysis {
|
|
1123
|
+
readonly consumedLength: number;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH = 32;
|
|
1127
|
+
|
|
1128
|
+
function analyzeProvablyRestrictedRegexBody(source: string): RestrictedRegexAnalysis | undefined {
|
|
1129
|
+
let index = 0;
|
|
1130
|
+
const parseAlternatives = (): RestrictedRegexAnalysis | undefined => {
|
|
1131
|
+
let maximumLength = 0;
|
|
1132
|
+
while (true) {
|
|
1133
|
+
const sequence = parseSequence();
|
|
1134
|
+
if (!sequence) return undefined;
|
|
1135
|
+
maximumLength = Math.max(maximumLength, sequence.maximumLength);
|
|
1136
|
+
if (source[index] !== "|") break;
|
|
1137
|
+
index += 1;
|
|
1138
|
+
}
|
|
1139
|
+
return { maximumLength };
|
|
1140
|
+
};
|
|
1141
|
+
const parseSequence = (): RestrictedRegexAnalysis | undefined => {
|
|
1142
|
+
let maximumLength = 0;
|
|
1143
|
+
while (index < source.length && source[index] !== "|" && source[index] !== ")") {
|
|
1144
|
+
const atom = parseAtom();
|
|
1145
|
+
if (!atom) return undefined;
|
|
1146
|
+
let maximumRepetitions = 1;
|
|
1147
|
+
if (source[index] === "?") {
|
|
1148
|
+
index += 1;
|
|
1149
|
+
} else if (source[index] === "{") {
|
|
1150
|
+
const match = /^\{(\d+)(?:,(\d+))?\}/.exec(source.slice(index));
|
|
1151
|
+
if (!match) return undefined;
|
|
1152
|
+
const minimum = Number(match[1]);
|
|
1153
|
+
maximumRepetitions = match[2] === undefined ? minimum : Number(match[2]);
|
|
1154
|
+
if (
|
|
1155
|
+
!Number.isSafeInteger(minimum) ||
|
|
1156
|
+
!Number.isSafeInteger(maximumRepetitions) ||
|
|
1157
|
+
maximumRepetitions < minimum
|
|
1158
|
+
)
|
|
1159
|
+
return undefined;
|
|
1160
|
+
index += match[0].length;
|
|
1161
|
+
}
|
|
1162
|
+
if (source[index] === "?") index += 1;
|
|
1163
|
+
maximumLength = addRestrictedRegexLengths(
|
|
1164
|
+
maximumLength,
|
|
1165
|
+
multiplyRestrictedRegexLength(atom.maximumLength, maximumRepetitions),
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
return { maximumLength };
|
|
1169
|
+
};
|
|
1170
|
+
const parseAtom = (): RestrictedRegexAnalysis | undefined => {
|
|
1171
|
+
if (source[index] === "(") {
|
|
1172
|
+
index += 1;
|
|
1173
|
+
if (source[index] === "?") {
|
|
1174
|
+
if (source.slice(index, index + 2) !== "?:") return undefined;
|
|
1175
|
+
index += 2;
|
|
1176
|
+
}
|
|
1177
|
+
const group = parseAlternatives();
|
|
1178
|
+
if (!group || source[index] !== ")") return undefined;
|
|
1179
|
+
index += 1;
|
|
1180
|
+
return group;
|
|
1181
|
+
}
|
|
1182
|
+
if (source[index] === "[") {
|
|
1183
|
+
const characterClass = analyzeSafeRegexCharacterClass(source.slice(index));
|
|
1184
|
+
if (!characterClass) return undefined;
|
|
1185
|
+
index += characterClass.consumedLength;
|
|
1186
|
+
return characterClass;
|
|
1187
|
+
}
|
|
1188
|
+
const atomLength = safeRegexAtomLength(source.slice(index));
|
|
1189
|
+
if (atomLength === 0) return undefined;
|
|
1190
|
+
index += atomLength;
|
|
1191
|
+
return { maximumLength: 1 };
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
const analysis = parseAlternatives();
|
|
1195
|
+
return analysis && index === source.length ? analysis : undefined;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function analyzeSafeRegexCharacterClass(
|
|
1199
|
+
source: string,
|
|
1200
|
+
): SafeRegexCharacterClassAnalysis | undefined {
|
|
1201
|
+
if (!source.startsWith("[") || source[1] === "^") return undefined;
|
|
1202
|
+
for (let index = 1; index < source.length; index += 1) {
|
|
1203
|
+
const character = source[index];
|
|
1204
|
+
if (character === "]") {
|
|
1205
|
+
return { consumedLength: index + 1, maximumLength: 1 };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const atomLength = safeRegexAtomLength(source.slice(index));
|
|
1209
|
+
if (atomLength === 0) return undefined;
|
|
1210
|
+
const rangeSeparator = index + atomLength;
|
|
1211
|
+
if (source[rangeSeparator] === "-" && source[rangeSeparator + 1] !== "]") {
|
|
1212
|
+
const endIndex = rangeSeparator + 1;
|
|
1213
|
+
const endLength = safeRegexAtomLength(source.slice(endIndex));
|
|
1214
|
+
if (endLength === 0 || !isSafeRegexCharacterRange(source, index, endIndex)) return undefined;
|
|
1215
|
+
index = endIndex + endLength - 1;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
index += atomLength - 1;
|
|
1219
|
+
}
|
|
1220
|
+
return undefined;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function addRestrictedRegexLengths(left: number, right: number): number {
|
|
1224
|
+
return left > MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH - right
|
|
1225
|
+
? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
|
|
1226
|
+
: left + right;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function multiplyRestrictedRegexLength(length: number, repetitions: number): number {
|
|
1230
|
+
if (length === 0 || repetitions === 0) return 0;
|
|
1231
|
+
return repetitions > Math.floor(MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH / length)
|
|
1232
|
+
? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
|
|
1233
|
+
: length * repetitions;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function safeRegexAtomLength(source: string): number {
|
|
1237
|
+
const character = source[0];
|
|
1238
|
+
if (character === undefined) return 0;
|
|
1239
|
+
if (/^[0-9]$/.test(character)) return 1;
|
|
1240
|
+
if (character === "-") return 1;
|
|
1241
|
+
if (character !== "\\") return 0;
|
|
1242
|
+
const escaped = source[1];
|
|
1243
|
+
if (escaped === "d") return 2;
|
|
1244
|
+
return escaped !== undefined && SAFE_REGEX_ESCAPED_PUNCTUATION.includes(escaped) ? 2 : 0;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function isSafeRegexCharacterRange(source: string, startIndex: number, endIndex: number): boolean {
|
|
1248
|
+
const start = regexCharacterClassLiteralCodePoint(source, startIndex);
|
|
1249
|
+
const end = regexCharacterClassLiteralCodePoint(source, endIndex);
|
|
1250
|
+
if (start === undefined || end === undefined || start > end) return false;
|
|
1251
|
+
const minimum = "0".codePointAt(0);
|
|
1252
|
+
const maximum = "9".codePointAt(0);
|
|
1253
|
+
return minimum !== undefined && maximum !== undefined && start >= minimum && end <= maximum;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function regexCharacterClassLiteralCodePoint(source: string, index: number): number | undefined {
|
|
1257
|
+
const character = source[index];
|
|
1258
|
+
if (character !== "\\") return character?.codePointAt(0);
|
|
1259
|
+
const escaped = source[index + 1];
|
|
1260
|
+
if (escaped === undefined || escaped === "d" || escaped === "w") return undefined;
|
|
1261
|
+
return escaped.codePointAt(0);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const SAFE_REGEX_ESCAPED_PUNCTUATION = "\\.-^$*+?()[]{}|";
|
|
1265
|
+
|
|
1266
|
+
function requireOutputTextTrustSchema(schema: unknown): InternalZodSchema {
|
|
1267
|
+
if (!(schema instanceof z.ZodType)) throw new OutputTextTrustSchemaError(schema);
|
|
1268
|
+
return asInternalZodSchema(schema);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function asInternalZodSchema(schema: z.core.$ZodType): InternalZodSchema {
|
|
1272
|
+
// Zod's child-definition fields use the base type and erase the concrete
|
|
1273
|
+
// union member. This is the only cast boundary; all callers immediately
|
|
1274
|
+
// narrow the returned discriminated definition by `def.type`.
|
|
1275
|
+
return schema as InternalZodSchema;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
function assertNeverZodDef(def: never): never {
|
|
1279
|
+
throw new Error(`Unsupported Zod definition: ${String((def as { type?: unknown }).type)}`);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function readTextTrustDeclaration(schema: unknown): TextTrustDeclaration {
|
|
1283
|
+
const metadata = readZodMetadata(schema);
|
|
1284
|
+
if (!metadata || !Reflect.has(metadata, APIFUSE_TEXT_TRUST_META_KEY)) return "absent";
|
|
1285
|
+
const value = Reflect.get(metadata, APIFUSE_TEXT_TRUST_META_KEY);
|
|
1286
|
+
if (!value || typeof value !== "object") return "invalid";
|
|
1287
|
+
if (Reflect.get(value, "v") !== 1) return "invalid";
|
|
1288
|
+
const trust = Reflect.get(value, "trust");
|
|
1289
|
+
return trust === "trusted" || trust === "untrusted" ? trust : "invalid";
|
|
1290
|
+
}
|
|
1291
|
+
|
|
241
1292
|
export function isSensitiveSchema(schema: unknown): boolean {
|
|
242
1293
|
const metadata = readZodMetadata(schema);
|
|
243
1294
|
return metadata !== undefined && Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true;
|