@juspay/neurolink 13.8.16 → 13.8.18

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +1 -1
  3. package/dist/action/actionExecutor.d.ts +33 -3
  4. package/dist/action/actionExecutor.js +71 -15
  5. package/dist/action/actionInputs.js +4 -1
  6. package/dist/action/cliVersion.d.ts +25 -0
  7. package/dist/action/cliVersion.js +26 -0
  8. package/dist/action/index.d.ts +2 -1
  9. package/dist/action/index.js +2 -1
  10. package/dist/adapters/video/directorPipeline.js +46 -36
  11. package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
  12. package/dist/adapters/video/ffmpegAdapter.js +32 -5
  13. package/dist/adapters/video/frameExtractor.js +1 -1
  14. package/dist/adapters/video/videoMerger.js +5 -2
  15. package/dist/agent/arithmeticEvaluator.d.ts +32 -0
  16. package/dist/agent/arithmeticEvaluator.js +349 -0
  17. package/dist/agent/directTools.d.ts +6 -18
  18. package/dist/agent/directTools.js +17 -56
  19. package/dist/artifacts/artifactStore.d.ts +22 -1
  20. package/dist/artifacts/artifactStore.js +66 -6
  21. package/dist/auth/anthropicOAuth.d.ts +26 -1
  22. package/dist/auth/anthropicOAuth.js +81 -5
  23. package/dist/auth/secureFiles.d.ts +37 -0
  24. package/dist/auth/secureFiles.js +95 -0
  25. package/dist/auth/tokenStore.d.ts +43 -6
  26. package/dist/auth/tokenStore.js +260 -79
  27. package/dist/avatar/providers/DIDAvatar.js +49 -45
  28. package/dist/avatar/providers/ReplicateAvatar.js +33 -40
  29. package/dist/browser/neurolink.min.js +485 -558
  30. package/dist/cli/commands/auth.js +112 -70
  31. package/dist/cli/commands/proxy.d.ts +4 -0
  32. package/dist/cli/commands/proxy.js +128 -13
  33. package/dist/cli/commands/serve.d.ts +10 -1
  34. package/dist/cli/commands/serve.js +98 -17
  35. package/dist/cli/commands/server.js +17 -5
  36. package/dist/cli/index.js +146 -5
  37. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  38. package/dist/cli/utils/envManager.js +17 -3
  39. package/dist/cli/utils/serverUtils.d.ts +39 -3
  40. package/dist/cli/utils/serverUtils.js +63 -6
  41. package/dist/core/baseProvider.js +7 -1
  42. package/dist/core/modules/MessageBuilder.js +5 -0
  43. package/dist/core/modules/ToolsManager.d.ts +46 -1
  44. package/dist/core/modules/ToolsManager.js +190 -43
  45. package/dist/files/fileReferenceRegistry.d.ts +2 -23
  46. package/dist/files/fileReferenceRegistry.js +55 -5
  47. package/dist/hitl/hitlManager.js +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +6 -1
  50. package/dist/mcp/auth/tokenStorage.d.ts +7 -0
  51. package/dist/mcp/auth/tokenStorage.js +23 -4
  52. package/dist/mcp/externalServerManager.d.ts +13 -3
  53. package/dist/mcp/externalServerManager.js +154 -136
  54. package/dist/mcp/index.d.ts +2 -0
  55. package/dist/mcp/index.js +3 -0
  56. package/dist/mcp/mcpClientFactory.d.ts +14 -0
  57. package/dist/mcp/mcpClientFactory.js +70 -7
  58. package/dist/mcp/projectConfigTrust.d.ts +72 -0
  59. package/dist/mcp/projectConfigTrust.js +237 -0
  60. package/dist/mcp/transportUrlPolicy.d.ts +39 -0
  61. package/dist/mcp/transportUrlPolicy.js +172 -0
  62. package/dist/music/providers/BeatovenMusic.js +35 -46
  63. package/dist/music/providers/ReplicateMusic.js +33 -40
  64. package/dist/neurolink.d.ts +16 -1
  65. package/dist/neurolink.js +64 -11
  66. package/dist/processors/archive/ArchiveProcessor.js +15 -1
  67. package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
  68. package/dist/processors/archive/zipEntryIndex.js +33 -0
  69. package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
  70. package/dist/processors/base/BaseFileProcessor.js +40 -111
  71. package/dist/processors/data/YamlProcessor.d.ts +13 -1
  72. package/dist/processors/data/YamlProcessor.js +93 -2
  73. package/dist/processors/document/OpenDocumentProcessor.js +7 -2
  74. package/dist/processors/document/PptxProcessor.js +12 -7
  75. package/dist/processors/media/VideoProcessor.js +93 -15
  76. package/dist/proxy/inboundAuth.d.ts +89 -0
  77. package/dist/proxy/inboundAuth.js +219 -0
  78. package/dist/proxy/tokenRefresh.d.ts +6 -0
  79. package/dist/proxy/tokenRefresh.js +13 -1
  80. package/dist/proxy/updateState.js +4 -4
  81. package/dist/rag/document/loaders.d.ts +0 -6
  82. package/dist/rag/document/loaders.js +16 -10
  83. package/dist/rag/ragIntegration.d.ts +2 -2
  84. package/dist/rag/ragIntegration.js +35 -10
  85. package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
  86. package/dist/rag/retrieval/vectorQueryTool.js +54 -17
  87. package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
  88. package/dist/server/abstract/baseServerAdapter.js +119 -6
  89. package/dist/server/adapters/expressAdapter.d.ts +3 -0
  90. package/dist/server/adapters/expressAdapter.js +83 -8
  91. package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
  92. package/dist/server/adapters/fastifyAdapter.js +165 -70
  93. package/dist/server/adapters/honoAdapter.d.ts +9 -1
  94. package/dist/server/adapters/honoAdapter.js +99 -21
  95. package/dist/server/adapters/koaAdapter.d.ts +1 -0
  96. package/dist/server/adapters/koaAdapter.js +153 -74
  97. package/dist/server/index.d.ts +2 -0
  98. package/dist/server/index.js +4 -0
  99. package/dist/server/middleware/auth.js +6 -2
  100. package/dist/server/middleware/cache.js +6 -1
  101. package/dist/server/middleware/rateLimit.js +29 -4
  102. package/dist/server/routes/agentRoutes.js +24 -11
  103. package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
  104. package/dist/server/routes/claudeProxyRoutes.js +273 -84
  105. package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
  106. package/dist/server/routes/codexProxyRoutes.js +125 -40
  107. package/dist/server/routes/index.d.ts +4 -0
  108. package/dist/server/routes/index.js +35 -4
  109. package/dist/server/routes/mcpRoutes.d.ts +6 -2
  110. package/dist/server/routes/mcpRoutes.js +21 -7
  111. package/dist/server/routes/memoryRoutes.js +7 -6
  112. package/dist/server/routes/toolRoutes.d.ts +12 -2
  113. package/dist/server/routes/toolRoutes.js +33 -3
  114. package/dist/server/routes/transcribeRoutes.js +60 -13
  115. package/dist/server/utils/clientAddress.d.ts +23 -0
  116. package/dist/server/utils/clientAddress.js +65 -0
  117. package/dist/server/utils/clientError.d.ts +35 -0
  118. package/dist/server/utils/clientError.js +129 -0
  119. package/dist/server/utils/expressBody.d.ts +33 -0
  120. package/dist/server/utils/expressBody.js +353 -0
  121. package/dist/server/utils/pathMatch.d.ts +21 -0
  122. package/dist/server/utils/pathMatch.js +41 -0
  123. package/dist/server/utils/validation.d.ts +9 -0
  124. package/dist/server/utils/validation.js +9 -0
  125. package/dist/server/websocket/WebSocketHandler.js +22 -3
  126. package/dist/server/websocket/frameQueue.d.ts +19 -0
  127. package/dist/server/websocket/frameQueue.js +41 -3
  128. package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
  129. package/dist/server/websocket/transcribeWebSocket.js +223 -34
  130. package/dist/skills/skillStores.js +5 -0
  131. package/dist/tasks/store/fileTaskStore.d.ts +2 -0
  132. package/dist/tasks/store/fileTaskStore.js +11 -4
  133. package/dist/types/attachments.d.ts +117 -0
  134. package/dist/types/attachments.js +15 -0
  135. package/dist/types/auth.d.ts +6 -0
  136. package/dist/types/cli.d.ts +4 -0
  137. package/dist/types/config.d.ts +56 -0
  138. package/dist/types/file.d.ts +8 -0
  139. package/dist/types/hitl.d.ts +8 -0
  140. package/dist/types/index.d.ts +1 -0
  141. package/dist/types/index.js +1 -0
  142. package/dist/types/mcp.d.ts +98 -0
  143. package/dist/types/multimodal.js +0 -41
  144. package/dist/types/processor.d.ts +14 -1
  145. package/dist/types/proxy.d.ts +62 -3
  146. package/dist/types/rag.d.ts +9 -0
  147. package/dist/types/safeFetch.d.ts +40 -0
  148. package/dist/types/server.d.ts +255 -10
  149. package/dist/types/tools.d.ts +57 -0
  150. package/dist/types/transcribe.d.ts +6 -0
  151. package/dist/utils/attachmentPolicy.d.ts +89 -0
  152. package/dist/utils/attachmentPolicy.js +327 -0
  153. package/dist/utils/dotenvBootstrap.d.ts +48 -19
  154. package/dist/utils/dotenvBootstrap.js +172 -31
  155. package/dist/utils/fileDetector.js +115 -65
  156. package/dist/utils/json/coerce.js +11 -1
  157. package/dist/utils/logSanitize.d.ts +5 -0
  158. package/dist/utils/logSanitize.js +8 -0
  159. package/dist/utils/logger.js +73 -1
  160. package/dist/utils/messageBuilder.d.ts +2 -2
  161. package/dist/utils/messageBuilder.js +118 -105
  162. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
  163. package/dist/utils/multimodalOptionsBuilder.js +2 -0
  164. package/dist/utils/redis.d.ts +12 -0
  165. package/dist/utils/redis.js +72 -0
  166. package/dist/utils/safeFetch.d.ts +22 -1
  167. package/dist/utils/safeFetch.js +132 -38
  168. package/dist/utils/safeStoreId.d.ts +19 -0
  169. package/dist/utils/safeStoreId.js +29 -0
  170. package/dist/utils/schemaConversion.d.ts +30 -3
  171. package/dist/utils/schemaConversion.js +457 -84
  172. package/dist/utils/sizeGuard.js +2 -2
  173. package/dist/utils/ssrfGuard.d.ts +3 -3
  174. package/dist/utils/ssrfGuard.js +71 -39
  175. package/dist/voice/eval/sttEvalStore.d.ts +26 -3
  176. package/dist/voice/eval/sttEvalStore.js +77 -12
  177. package/dist/voice/livekit/agentDefinition.d.ts +18 -0
  178. package/dist/voice/livekit/agentDefinition.js +22 -0
  179. package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
  180. package/dist/voice/livekit/realtimeMcpTools.js +8 -7
  181. package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
  182. package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
  183. package/dist/voice/livekit/voiceAgent.d.ts +2 -2
  184. package/dist/voice/livekit/voiceAgent.js +2 -1
  185. package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
  186. package/dist/voice/livekit/xaiSessionWatch.js +30 -0
  187. package/dist/voice/providers/GeminiLive.js +21 -6
  188. package/dist/voice/providers/GoogleSTT.js +8 -5
  189. package/dist/voice/transcribe.d.ts +2 -2
  190. package/dist/voice/transcribe.js +34 -18
  191. package/dist/voice/whistle/audio.js +11 -5
  192. package/docs-site/static/search-index.json +75 -52
  193. package/package.json +171 -156
  194. package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
@@ -2,30 +2,37 @@ import { createRequire } from "node:module";
2
2
  import * as zodModule from "zod";
3
3
  import { z } from "zod";
4
4
  import { logger } from "./logger.js";
5
+ import { vetRegexPattern } from "../middleware/utils/regexSafety.js";
5
6
  /**
6
- * zod-to-json-schema and json-schema-to-zod are loaded synchronously via
7
- * require() on first actual use, not via a top-level import, so merely
8
- * importing this module no longer pulls in either package for the many
9
- * callers who never convert a schema. Both ship a CJS `require` export
10
- * condition (checked against their package.json), so require() works on
11
- * any Node >=22.0.0 — no experimental flag, unlike require(esm) of a local
12
- * ES module (which only works unflagged from Node >=22.12). Zod itself
13
- * stays a normal static import: its runtime schemas (`z`, `zodModule`) are
14
- * public exports used well beyond schema conversion. Follows the pattern in
15
- * src/lib/proxy/otelLogSink.ts — construction stays one synchronous call,
16
- * exactly as before; only the *import* is deferred.
7
+ * zod-to-json-schema is loaded synchronously via require() on first actual
8
+ * use, not via a top-level import, so merely importing this module no longer
9
+ * pulls in the package for the many callers who never convert a schema. It
10
+ * ships a CJS `require` export condition (checked against its package.json),
11
+ * so require() works on any Node >=22.0.0 — no experimental flag, unlike
12
+ * require(esm) of a local ES module (which only works unflagged from Node
13
+ * >=22.12). Zod itself stays a normal static import: its runtime schemas
14
+ * (`z`, `zodModule`) are public exports used well beyond schema conversion.
15
+ * Follows the pattern in src/lib/proxy/otelLogSink.ts — construction stays
16
+ * one synchronous call, exactly as before; only the *import* is deferred.
17
+ *
18
+ * The reverse direction (JSON Schema → Zod, `convertJsonSchemaToZod`) no
19
+ * longer uses a package at all. It used to run `json-schema-to-zod`'s
20
+ * generated source through `new Function`; the input to that generator is a
21
+ * tool schema a host or a remote MCP server supplies, so the process's safety
22
+ * rested on the generator escaping every string position correctly. The
23
+ * converter below builds the Zod schema directly from the JSON Schema object
24
+ * and never evaluates text.
17
25
  *
18
26
  * A require() made through createRequire() is invisible to bundlers, so a
19
- * consumer that bundles the SDK without these packages next to the output can
20
- * no longer resolve them, and listing them as external changes nothing for the
27
+ * consumer that bundles the SDK without the package next to the output can
28
+ * no longer resolve it, and listing it as external changes nothing for the
21
29
  * same reason: the bundler never sees the call. That must not pass for a
22
- * schema that simply had no properties: the failure is reported once per
23
- * package at error level (the only level shown without NEUROLINK_DEBUG),
24
- * remembered so later calls neither retry the lookup nor repeat the report,
25
- * and the caller degrades exactly as it does when a conversion throws. The
26
- * browser artifact is the one bundle we build ourselves;
27
- * scripts/build-browser.mjs bundles both packages into it and serves them from
28
- * its `module` stub.
30
+ * schema that simply had no properties: the failure is reported once at error
31
+ * level (the only level shown without NEUROLINK_DEBUG), remembered so later
32
+ * calls neither retry the lookup nor repeat the report, and the caller
33
+ * degrades exactly as it does when a conversion throws. The browser artifact
34
+ * is the one bundle we build ourselves; scripts/build-browser.mjs bundles the
35
+ * package into it and serves it from its `module` stub.
29
36
  */
30
37
  const require = createRequire(import.meta.url);
31
38
  function lazyPackageFunction(packageName, exportName) {
@@ -64,7 +71,6 @@ function lazyPackageFunction(packageName, exportName) {
64
71
  };
65
72
  }
66
73
  const loadZodToJsonSchema = lazyPackageFunction("zod-to-json-schema", "zodToJsonSchema");
67
- const loadJsonSchemaToZod = lazyPackageFunction("json-schema-to-zod", "jsonSchemaToZod");
68
74
  // Zod 4 ships a built-in `z.toJSONSchema(...)`. Zod 3 does not — it returned
69
75
  // nothing of the sort and we relied entirely on `zod-to-json-schema`. The
70
76
  // `zod-to-json-schema` package only understands Zod 3's internal `_def`
@@ -649,88 +655,455 @@ export function isZodSchema(value) {
649
655
  export function isZod4Schema(value) {
650
656
  return !!(value && typeof value === "object" && "_zod" in value);
651
657
  }
658
+ // ---------------------------------------------------------------------------
659
+ // JSON Schema → Zod, without evaluating generated code
660
+ // ---------------------------------------------------------------------------
661
+ /** Deepest JSON Schema nesting the converter follows before giving up. */
662
+ const MAX_JSON_SCHEMA_DEPTH = 32;
652
663
  /**
653
- * Convert JSON Schema to Zod schema format using official json-schema-to-zod library
654
- * This ensures complete preservation of all schema structure and validation rules
664
+ * Property names that must never become keys of a Zod object shape. Building a
665
+ * shape with `shape[name] = schema` for `__proto__` would replace the shape's
666
+ * prototype instead of adding a property, and Zod reads the shape by its keys.
667
+ * A tool whose argument is literally called `__proto__` is hostile, not real.
668
+ */
669
+ const FORBIDDEN_PROPERTY_NAMES = new Set([
670
+ "__proto__",
671
+ "constructor",
672
+ "prototype",
673
+ ]);
674
+ function isSchemaConversionNode(value) {
675
+ return value !== null && typeof value === "object" && !Array.isArray(value);
676
+ }
677
+ function isLiteralValue(value) {
678
+ return (value === null ||
679
+ typeof value === "string" ||
680
+ typeof value === "number" ||
681
+ typeof value === "boolean");
682
+ }
683
+ /**
684
+ * Resolve a local `#/definitions/x` or `#/$defs/x` pointer against the root.
685
+ * Anything else — a remote `$ref`, a pointer into an unknown place — resolves
686
+ * to `undefined` and the caller falls back to `z.unknown()`.
687
+ */
688
+ function resolveLocalRef(ref, root) {
689
+ if (!ref.startsWith("#/")) {
690
+ return undefined;
691
+ }
692
+ const segments = ref
693
+ .slice(2)
694
+ .split("/")
695
+ .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
696
+ let current = root;
697
+ for (const segment of segments) {
698
+ if (!isSchemaConversionNode(current) ||
699
+ FORBIDDEN_PROPERTY_NAMES.has(segment)) {
700
+ return undefined;
701
+ }
702
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) {
703
+ return undefined;
704
+ }
705
+ current = current[segment];
706
+ }
707
+ return isSchemaConversionNode(current) ? current : undefined;
708
+ }
709
+ function literalSchema(value) {
710
+ return value === null ? z.null() : z.literal(value);
711
+ }
712
+ function unionOf(members) {
713
+ if (members.length === 0) {
714
+ return z.unknown();
715
+ }
716
+ if (members.length === 1) {
717
+ return members[0];
718
+ }
719
+ return z.union(members);
720
+ }
721
+ function convertStringSchema(schema, path, context) {
722
+ let result = z.string();
723
+ if (typeof schema.minLength === "number" && schema.minLength >= 0) {
724
+ result = result.min(Math.floor(schema.minLength));
725
+ }
726
+ if (typeof schema.maxLength === "number" && schema.maxLength >= 0) {
727
+ result = result.max(Math.floor(schema.maxLength));
728
+ }
729
+ if (typeof schema.pattern === "string") {
730
+ const verdict = vetRegexPattern(schema.pattern);
731
+ if (verdict.safe) {
732
+ try {
733
+ result = result.regex(new RegExp(schema.pattern, "u"));
734
+ }
735
+ catch {
736
+ context.onRegexDrop({
737
+ path: `${path}/pattern`,
738
+ pattern: schema.pattern,
739
+ reason: "the pattern is not a valid regular expression",
740
+ });
741
+ }
742
+ }
743
+ else {
744
+ context.onRegexDrop({
745
+ path: `${path}/pattern`,
746
+ pattern: schema.pattern,
747
+ reason: verdict.reason,
748
+ });
749
+ }
750
+ }
751
+ return result;
752
+ }
753
+ function convertNumberSchema(schema, integer) {
754
+ let result = integer ? z.number().int() : z.number();
755
+ if (typeof schema.minimum === "number") {
756
+ result = result.min(schema.minimum);
757
+ }
758
+ if (typeof schema.maximum === "number") {
759
+ result = result.max(schema.maximum);
760
+ }
761
+ if (typeof schema.exclusiveMinimum === "number") {
762
+ result = result.gt(schema.exclusiveMinimum);
763
+ }
764
+ if (typeof schema.exclusiveMaximum === "number") {
765
+ result = result.lt(schema.exclusiveMaximum);
766
+ }
767
+ if (typeof schema.multipleOf === "number" && schema.multipleOf > 0) {
768
+ result = result.multipleOf(schema.multipleOf);
769
+ }
770
+ return result;
771
+ }
772
+ function convertArraySchema(schema, path, context, depth) {
773
+ if (Array.isArray(schema.items)) {
774
+ const members = schema.items.map((item, index) => convertSchemaNode(item, `${path}/items/${index}`, context, depth + 1));
775
+ if (members.length === 0) {
776
+ return z.tuple([]);
777
+ }
778
+ return z.tuple(members);
779
+ }
780
+ const itemSchema = schema.items === undefined
781
+ ? z.unknown()
782
+ : convertSchemaNode(schema.items, `${path}/items`, context, depth + 1);
783
+ let result = z.array(itemSchema);
784
+ if (typeof schema.minItems === "number" && schema.minItems >= 0) {
785
+ result = result.min(Math.floor(schema.minItems));
786
+ }
787
+ if (typeof schema.maxItems === "number" && schema.maxItems >= 0) {
788
+ result = result.max(Math.floor(schema.maxItems));
789
+ }
790
+ return result;
791
+ }
792
+ function convertObjectSchema(schema, path, context, depth) {
793
+ const required = new Set(Array.isArray(schema.required)
794
+ ? schema.required.filter((name) => typeof name === "string")
795
+ : []);
796
+ const shape = {};
797
+ if (isSchemaConversionNode(schema.properties)) {
798
+ for (const [name, propertySchema] of Object.entries(schema.properties)) {
799
+ if (FORBIDDEN_PROPERTY_NAMES.has(name)) {
800
+ logger.debug(`[SCHEMA-CONVERSION] Dropped property "${name}" at ${path}: the name is reserved`);
801
+ continue;
802
+ }
803
+ const converted = convertSchemaNode(propertySchema, `${path}/properties/${name}`, context, depth + 1);
804
+ shape[name] = required.has(name) ? converted : converted.optional();
805
+ }
806
+ }
807
+ const base = z.object(shape);
808
+ const additional = schema.additionalProperties;
809
+ if (additional === false) {
810
+ return base.strict();
811
+ }
812
+ if (additional === true) {
813
+ return base.passthrough();
814
+ }
815
+ if (isSchemaConversionNode(additional)) {
816
+ return base.catchall(convertSchemaNode(additional, `${path}/additionalProperties`, context, depth + 1));
817
+ }
818
+ return base;
819
+ }
820
+ function convertTypedSchema(type, schema, path, context, depth) {
821
+ switch (type) {
822
+ case "string":
823
+ return convertStringSchema(schema, path, context);
824
+ case "number":
825
+ return convertNumberSchema(schema, false);
826
+ case "integer":
827
+ return convertNumberSchema(schema, true);
828
+ case "boolean":
829
+ return z.boolean();
830
+ case "null":
831
+ return z.null();
832
+ case "array":
833
+ return convertArraySchema(schema, path, context, depth);
834
+ case "object":
835
+ return convertObjectSchema(schema, path, context, depth);
836
+ default:
837
+ return z.unknown();
838
+ }
839
+ }
840
+ function convertSchemaNode(node, path, context, depth) {
841
+ if (node === true || node === undefined) {
842
+ return z.unknown();
843
+ }
844
+ if (node === false) {
845
+ return z.never();
846
+ }
847
+ if (!isSchemaConversionNode(node)) {
848
+ return z.unknown();
849
+ }
850
+ if (depth > MAX_JSON_SCHEMA_DEPTH) {
851
+ logger.debug(`[SCHEMA-CONVERSION] Schema at ${path} nests deeper than ${MAX_JSON_SCHEMA_DEPTH}; accepting any value there`);
852
+ return z.unknown();
853
+ }
854
+ let result;
855
+ if (typeof node.$ref === "string") {
856
+ const target = resolveLocalRef(node.$ref, context.root);
857
+ if (target === undefined || context.refStack.has(node.$ref)) {
858
+ result = z.unknown();
859
+ }
860
+ else {
861
+ context.refStack.add(node.$ref);
862
+ try {
863
+ result = convertSchemaNode(target, path, context, depth + 1);
864
+ }
865
+ finally {
866
+ context.refStack.delete(node.$ref);
867
+ }
868
+ }
869
+ }
870
+ else if (Array.isArray(node.enum)) {
871
+ const literals = node.enum.filter(isLiteralValue);
872
+ const allStrings = literals.length > 0 &&
873
+ literals.length === node.enum.length &&
874
+ literals.every((value) => typeof value === "string");
875
+ result = allStrings
876
+ ? z.enum(literals)
877
+ : unionOf(literals
878
+ .map(literalSchema)
879
+ .concat(literals.length === node.enum.length ? [] : [z.unknown()]));
880
+ }
881
+ else if ("const" in node) {
882
+ result = isLiteralValue(node.const)
883
+ ? literalSchema(node.const)
884
+ : z.unknown();
885
+ }
886
+ else if (Array.isArray(node.anyOf) || Array.isArray(node.oneOf)) {
887
+ const options = (node.anyOf ?? node.oneOf);
888
+ const keyword = Array.isArray(node.anyOf) ? "anyOf" : "oneOf";
889
+ result = unionOf(options.map((option, index) => convertSchemaNode(option, `${path}/${keyword}/${index}`, context, depth + 1)));
890
+ }
891
+ else if (Array.isArray(node.allOf)) {
892
+ const parts = node.allOf.map((part, index) => convertSchemaNode(part, `${path}/allOf/${index}`, context, depth + 1));
893
+ result =
894
+ parts.length === 0
895
+ ? z.unknown()
896
+ : parts.reduce((left, right) => z.intersection(left, right));
897
+ }
898
+ else if (Array.isArray(node.type)) {
899
+ const types = node.type.filter((entry) => typeof entry === "string");
900
+ result = unionOf(types.map((type) => convertTypedSchema(type, node, path, context, depth)));
901
+ }
902
+ else if (typeof node.type === "string") {
903
+ result = convertTypedSchema(node.type, node, path, context, depth);
904
+ }
905
+ else if (isSchemaConversionNode(node.properties)) {
906
+ result = convertObjectSchema(node, path, context, depth);
907
+ }
908
+ else if (node.items !== undefined) {
909
+ result = convertArraySchema(node, path, context, depth);
910
+ }
911
+ else {
912
+ result = z.unknown();
913
+ }
914
+ if (node.nullable === true) {
915
+ result = result.nullable();
916
+ }
917
+ if (typeof node.description === "string") {
918
+ result = result.describe(node.description);
919
+ }
920
+ if ("default" in node && node.default !== undefined) {
921
+ result = result.default(node.default);
922
+ }
923
+ return result;
924
+ }
925
+ /**
926
+ * Convert a JSON Schema object to a Zod schema.
927
+ *
928
+ * Built directly from the schema object — no generated source, no
929
+ * `new Function` — so a hostile `description`, `pattern`, `default`, `const`,
930
+ * `enum` or property name is data, never code. Covers the subset tool
931
+ * schemas use: object/array/string/number/integer/boolean/null, `enum`,
932
+ * `const`, `anyOf`/`oneOf`/`allOf`, type unions, local `$ref`s, `nullable`,
933
+ * `required`, `additionalProperties`, `description`, `default` and the common
934
+ * length/range bounds. A `pattern` is compiled only after `vetRegexPattern`
935
+ * accepts it; a pattern that could stall the process is dropped from the
936
+ * schema and reported once at warn level. Unknown constructs validate as
937
+ * `unknown` rather than failing the conversion.
938
+ *
939
+ * Returns `z.object({}).passthrough()` for input that is not a schema object,
940
+ * exactly as the previous implementation did on any failure.
655
941
  */
656
942
  export function convertJsonSchemaToZod(jsonSchema) {
657
943
  const startTime = Date.now();
944
+ if (!isSchemaConversionNode(jsonSchema)) {
945
+ logger.debug("🔍 [SCHEMA-CONVERSION] Invalid or empty JSON schema, using fallback");
946
+ return z.object({}).passthrough();
947
+ }
948
+ const drops = [];
658
949
  try {
659
- // Handle empty or invalid schemas
660
- if (!jsonSchema || typeof jsonSchema !== "object") {
661
- logger.debug("🔍 [SCHEMA-CONVERSION] Invalid or empty JSON schema, using fallback");
662
- return z.object({}).passthrough();
663
- }
664
- // Log detailed input schema for debugging
665
- logger.debug("🔍 [SCHEMA-CONVERSION] ===== STARTING OFFICIAL LIBRARY CONVERSION =====");
666
- logger.debug("🔍 [SCHEMA-CONVERSION] Input JSON Schema:", {
667
- type: jsonSchema.type,
668
- hasProperties: !!jsonSchema.properties,
669
- propertiesCount: jsonSchema.properties
670
- ? Object.keys(jsonSchema.properties).length
671
- : 0,
672
- requiredCount: Array.isArray(jsonSchema.required)
673
- ? jsonSchema.required.length
674
- : 0,
675
- required: jsonSchema.required,
676
- sampleProperties: jsonSchema.properties
677
- ? Object.keys(jsonSchema.properties).slice(0, 5)
678
- : [],
679
- });
680
- // Use official library to convert JSON Schema to Zod code
681
- const convertToZodCode = loadJsonSchemaToZod();
682
- if (!convertToZodCode) {
683
- return z.object({}).passthrough();
684
- }
685
- const zodCodeResult = convertToZodCode(jsonSchema, {
686
- module: "esm",
687
- name: "schema",
688
- });
689
- logger.debug("🔍 [SCHEMA-CONVERSION] Generated Zod code:", {
690
- codeLength: zodCodeResult.length,
691
- codePreview: zodCodeResult.substring(0, 200) + "...",
692
- });
693
- // Extract the actual Zod schema expression from the generated code
694
- // Generated code looks like: "import { z } from "zod"\n\nexport const schema = z.object({...})\n"
695
- const schemaMatch = zodCodeResult.match(/export const schema = (z\..+?)(?:\n|$)/s);
696
- if (!schemaMatch) {
697
- throw new Error("Could not extract Zod schema from generated code");
698
- }
699
- const schemaExpression = schemaMatch[1].trim();
700
- logger.debug("🔍 [SCHEMA-CONVERSION] Extracted schema expression:", {
701
- expression: schemaExpression.substring(0, 300) + "...",
702
- });
703
- // Use Function constructor instead of eval for better scope control
704
- const createZodSchema = new Function("z", `return ${schemaExpression}`);
705
- const zodSchema = createZodSchema(z);
706
- const conversionTime = Date.now() - startTime;
950
+ const zodSchema = convertSchemaNode(jsonSchema, "#", {
951
+ root: jsonSchema,
952
+ refStack: new Set(),
953
+ onRegexDrop: (drop) => drops.push(drop),
954
+ }, 0);
955
+ if (drops.length > 0) {
956
+ logger.warn(`[SCHEMA-CONVERSION] Dropped ${drops.length} regular expression pattern(s) that could stall the process; the affected fields validate without them`, {
957
+ drops: drops.map(({ path, reason }) => ({ path, reason })),
958
+ });
959
+ }
707
960
  logger.debug("🔍 [SCHEMA-CONVERSION] ===== CONVERSION SUCCESSFUL =====", {
708
961
  inputType: jsonSchema.type,
709
- propertiesCount: jsonSchema.properties
962
+ propertiesCount: isSchemaConversionNode(jsonSchema.properties)
710
963
  ? Object.keys(jsonSchema.properties).length
711
964
  : 0,
712
965
  requiredCount: Array.isArray(jsonSchema.required)
713
966
  ? jsonSchema.required.length
714
967
  : 0,
715
- conversionSuccess: true,
716
- conversionTimeMs: conversionTime,
717
- libraryUsed: "json-schema-to-zod-official",
718
- zodSchemaType: zodSchema?.constructor?.name || "unknown",
968
+ droppedPatterns: drops.length,
969
+ conversionTimeMs: Date.now() - startTime,
719
970
  });
720
971
  return zodSchema;
721
972
  }
722
973
  catch (error) {
723
- const conversionTime = Date.now() - startTime;
724
- logger.warn("🚨 [SCHEMA-CONVERSION] Official library conversion failed, using passthrough fallback", {
974
+ logger.warn("🚨 [SCHEMA-CONVERSION] JSON Schema conversion failed, using passthrough fallback", {
725
975
  error: error instanceof Error ? error.message : String(error),
726
976
  errorType: error instanceof Error ? error.constructor.name : typeof error,
727
- inputSchemaType: jsonSchema?.type,
728
- inputSchemaKeys: jsonSchema && typeof jsonSchema === "object"
729
- ? Object.keys(jsonSchema)
730
- : [],
731
- conversionTimeMs: conversionTime,
732
- libraryUsed: "json-schema-to-zod-official-FAILED",
977
+ inputSchemaType: jsonSchema.type,
978
+ inputSchemaKeys: Object.keys(jsonSchema),
979
+ conversionTimeMs: Date.now() - startTime,
733
980
  });
734
981
  return z.object({}).passthrough();
735
982
  }
736
983
  }
984
+ /**
985
+ * Keys whose string value is a regular expression source in JSON Schema.
986
+ * `patternProperties` is handled separately: there the KEYS are the patterns.
987
+ */
988
+ const REGEX_VALUED_KEYWORDS = new Set(["pattern"]);
989
+ function collectUnsafePatterns(node, path, depth, drops) {
990
+ if (depth > MAX_JSON_SCHEMA_DEPTH ||
991
+ node === null ||
992
+ typeof node !== "object") {
993
+ return;
994
+ }
995
+ if (Array.isArray(node)) {
996
+ node.forEach((item, index) => collectUnsafePatterns(item, `${path}/${index}`, depth + 1, drops));
997
+ return;
998
+ }
999
+ for (const [key, value] of Object.entries(node)) {
1000
+ if (REGEX_VALUED_KEYWORDS.has(key) && typeof value === "string") {
1001
+ const verdict = vetRegexPattern(value);
1002
+ if (!verdict.safe) {
1003
+ drops.push({
1004
+ path: `${path}/${key}`,
1005
+ pattern: value,
1006
+ reason: verdict.reason,
1007
+ });
1008
+ }
1009
+ continue;
1010
+ }
1011
+ if (key === "patternProperties" && isSchemaConversionNode(value)) {
1012
+ for (const [pattern, subSchema] of Object.entries(value)) {
1013
+ const verdict = vetRegexPattern(pattern);
1014
+ if (!verdict.safe) {
1015
+ drops.push({
1016
+ path: `${path}/patternProperties/${pattern}`,
1017
+ pattern,
1018
+ reason: verdict.reason,
1019
+ });
1020
+ }
1021
+ collectUnsafePatterns(subSchema, `${path}/patternProperties/${pattern}`, depth + 1, drops);
1022
+ }
1023
+ continue;
1024
+ }
1025
+ collectUnsafePatterns(value, `${path}/${key}`, depth + 1, drops);
1026
+ }
1027
+ }
1028
+ function removeUnsafePatterns(node, unsafe, path, depth) {
1029
+ if (depth > MAX_JSON_SCHEMA_DEPTH ||
1030
+ node === null ||
1031
+ typeof node !== "object") {
1032
+ return node;
1033
+ }
1034
+ if (Array.isArray(node)) {
1035
+ return node.map((item, index) => removeUnsafePatterns(item, unsafe, `${path}/${index}`, depth + 1));
1036
+ }
1037
+ const out = {};
1038
+ for (const [key, value] of Object.entries(node)) {
1039
+ if (REGEX_VALUED_KEYWORDS.has(key) && typeof value === "string") {
1040
+ if (unsafe.has(`${path}/${key}`)) {
1041
+ continue;
1042
+ }
1043
+ Object.defineProperty(out, key, {
1044
+ value,
1045
+ enumerable: true,
1046
+ writable: true,
1047
+ configurable: true,
1048
+ });
1049
+ continue;
1050
+ }
1051
+ if (key === "patternProperties" && isSchemaConversionNode(value)) {
1052
+ const kept = {};
1053
+ for (const [pattern, subSchema] of Object.entries(value)) {
1054
+ const subPath = `${path}/patternProperties/${pattern}`;
1055
+ if (unsafe.has(subPath)) {
1056
+ continue;
1057
+ }
1058
+ Object.defineProperty(kept, pattern, {
1059
+ value: removeUnsafePatterns(subSchema, unsafe, subPath, depth + 1),
1060
+ enumerable: true,
1061
+ writable: true,
1062
+ configurable: true,
1063
+ });
1064
+ }
1065
+ Object.defineProperty(out, key, {
1066
+ value: kept,
1067
+ enumerable: true,
1068
+ writable: true,
1069
+ configurable: true,
1070
+ });
1071
+ continue;
1072
+ }
1073
+ Object.defineProperty(out, key, {
1074
+ value: removeUnsafePatterns(value, unsafe, `${path}/${key}`, depth + 1),
1075
+ enumerable: true,
1076
+ writable: true,
1077
+ configurable: true,
1078
+ });
1079
+ }
1080
+ return out;
1081
+ }
1082
+ /**
1083
+ * Remove from a JSON Schema every `pattern` and every `patternProperties` key
1084
+ * that `vetRegexPattern` refuses, so a validator built from the result (the
1085
+ * `@cfworker/json-schema` validator compiles each with `new RegExp` on every
1086
+ * check) cannot be stalled by a schema a remote server chose.
1087
+ *
1088
+ * Returns the input object itself when nothing had to be removed, so the
1089
+ * common case costs one walk and no allocation. Otherwise a deep copy with the
1090
+ * offending keywords left out, and `drops` names each one. Never throws.
1091
+ */
1092
+ export function stripUnsafeRegexPatterns(schema) {
1093
+ const drops = [];
1094
+ try {
1095
+ collectUnsafePatterns(schema, "#", 0, drops);
1096
+ if (drops.length === 0) {
1097
+ return { schema, drops };
1098
+ }
1099
+ const unsafe = new Set(drops.map((drop) => drop.path));
1100
+ const stripped = removeUnsafePatterns(schema, unsafe, "#", 0);
1101
+ return {
1102
+ schema: isSchemaConversionNode(stripped) ? stripped : schema,
1103
+ drops,
1104
+ };
1105
+ }
1106
+ catch {
1107
+ return { schema, drops };
1108
+ }
1109
+ }
@@ -37,9 +37,9 @@ export const MAX_IMAGE_BYTES = 25 * 1024 * 1024;
37
37
  export async function readBoundedBuffer(response, maxBytes, label) {
38
38
  const contentLength = parseInt(response.headers.get("content-length") ?? "0", 10);
39
39
  if (contentLength > 0 && contentLength > maxBytes) {
40
- throw new Error(`${label} download too large: ${contentLength} bytes (max ${maxBytes})`);
40
+ throw new Error(`File too large: ${label} declares ${contentLength} bytes, over the ${maxBytes}-byte size cap`);
41
41
  }
42
- const exceeded = (bytes) => new Error(`${label} download exceeded size cap after fetch: ${bytes} bytes (max ${maxBytes})`);
42
+ const exceeded = (bytes) => new Error(`File too large: ${label} exceeded the ${maxBytes}-byte size cap after fetch (${bytes} bytes received)`);
43
43
  const body = response.body;
44
44
  if (!body || typeof body.getReader !== "function") {
45
45
  const buffer = Buffer.from(await response.arrayBuffer());
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @module utils/ssrfGuard
26
26
  */
27
- import type { PinnedAddress } from "../types/index.js";
27
+ import type { PinnedAddress, SsrfGuardOptions } from "../types/index.js";
28
28
  /**
29
29
  * Assert that `url` is safe to fetch server-side.
30
30
  *
@@ -50,7 +50,7 @@ export declare function assertSafeUrl(url: string): Promise<void>;
50
50
  *
51
51
  * Same throw semantics as {@link assertSafeUrl}.
52
52
  */
53
- export declare function assertSafeUrlForProxy(url: string): void;
53
+ export declare function assertSafeUrlForProxy(url: string, options?: SsrfGuardOptions): void;
54
54
  /**
55
55
  * Validate `url` and return the resolved address set that should be used for
56
56
  * the actual fetch (companion to `safeFetch.ts:safeDownload`).
@@ -68,7 +68,7 @@ export declare function assertSafeUrlForProxy(url: string): void;
68
68
  * This is the canonical entry point for binary downloads where DNS-rebinding
69
69
  * pinning matters — see `safeFetch.ts`.
70
70
  */
71
- export declare function validateAndResolveUrl(url: string): Promise<{
71
+ export declare function validateAndResolveUrl(url: string, options?: SsrfGuardOptions): Promise<{
72
72
  url: string;
73
73
  ip: string;
74
74
  family: 4 | 6;