@juspay/neurolink 13.8.17 → 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.
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +78 -6
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +62 -3
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +206 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/docs-site/static/search-index.json +71 -49
- package/package.json +171 -156
- 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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
20
|
-
* no longer resolve
|
|
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
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
-
*
|
|
654
|
-
*
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
716
|
-
conversionTimeMs:
|
|
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
|
-
|
|
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
|
|
728
|
-
inputSchemaKeys: jsonSchema
|
|
729
|
-
|
|
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
|
+
}
|
package/dist/utils/sizeGuard.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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;
|