@zuplo/cli 7.5.0 → 7.5.2

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 (34) hide show
  1. package/node_modules/@posthog/types/dist/posthog-config.d.ts +16 -4
  2. package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
  3. package/node_modules/@posthog/types/package.json +1 -1
  4. package/node_modules/@posthog/types/src/posthog-config.ts +17 -4
  5. package/node_modules/@zuplo/core/customer.cli.minified.js +1 -1
  6. package/node_modules/@zuplo/core/index.minified.js +1 -1
  7. package/node_modules/@zuplo/core/package.json +1 -1
  8. package/node_modules/@zuplo/graphql/package.json +1 -1
  9. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  10. package/node_modules/@zuplo/otel/package.json +1 -1
  11. package/node_modules/@zuplo/runtime/out/esm/chunk-HEFHZ2HU.js +409 -0
  12. package/node_modules/@zuplo/runtime/out/esm/chunk-HEFHZ2HU.js.map +1 -0
  13. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  14. package/node_modules/@zuplo/runtime/out/esm/index.js.map +1 -1
  15. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
  16. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js.map +1 -1
  17. package/node_modules/@zuplo/runtime/out/types/index.d.ts +253 -0
  18. package/node_modules/@zuplo/runtime/package.json +1 -1
  19. package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
  20. package/node_modules/hono/dist/cjs/helper/ssg/utils.js +30 -10
  21. package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
  22. package/node_modules/hono/dist/cjs/utils/body.js +15 -3
  23. package/node_modules/hono/dist/cjs/utils/url.js +9 -1
  24. package/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
  25. package/node_modules/hono/dist/helper/ssg/utils.js +30 -10
  26. package/node_modules/hono/dist/middleware/cache/index.js +1 -1
  27. package/node_modules/hono/dist/types/utils/url.d.ts +4 -0
  28. package/node_modules/hono/dist/utils/body.js +15 -3
  29. package/node_modules/hono/dist/utils/url.js +9 -1
  30. package/node_modules/hono/package.json +1 -1
  31. package/package.json +11 -11
  32. package/node_modules/@zuplo/runtime/out/esm/chunk-COF23LJ5.js +0 -403
  33. package/node_modules/@zuplo/runtime/out/esm/chunk-COF23LJ5.js.map +0 -1
  34. /package/node_modules/@zuplo/runtime/out/esm/{chunk-COF23LJ5.js.LEGAL.txt → chunk-HEFHZ2HU.js.LEGAL.txt} +0 -0
@@ -1065,6 +1065,154 @@ export declare interface AIGatewaySemanticCacheV2InboundPolicyOptions {
1065
1065
  onUnknownShape?: OnUnknownShape_2;
1066
1066
  }
1067
1067
 
1068
+ /**
1069
+ * Reads and writes the Smart Router result for an AI Gateway request.
1070
+ *
1071
+ * The built-in Smart Router policy `set()`s this after a successful
1072
+ * classification. Custom policies can `get()` it later in the same request,
1073
+ * or `set()` a compatible result of their own.
1074
+ *
1075
+ * @public
1076
+ * @example
1077
+ * ```typescript
1078
+ * import { AIGatewaySmartRouter } from "@zuplo/runtime";
1079
+ *
1080
+ * const result = AIGatewaySmartRouter.get(context);
1081
+ * if (result?.profile.intent === "code") {
1082
+ * // ...
1083
+ * }
1084
+ * ```
1085
+ */
1086
+ export declare const AIGatewaySmartRouter: {
1087
+ set(context: ZuploContext, result: AIGatewaySmartRouterResult): void;
1088
+ get(context: ZuploContext): AIGatewaySmartRouterResult | undefined;
1089
+ };
1090
+
1091
+ /**
1092
+ * Prompt-complexity label produced by Smart Router.
1093
+ *
1094
+ * @public
1095
+ */
1096
+ export declare type AIGatewaySmartRouterComplexity = "low" | "medium" | "high";
1097
+
1098
+ /**
1099
+ * Smart Router Inbound Policy for the AI Gateway
1100
+ * @title Smart Router
1101
+ * @product ai-gateway
1102
+ * @requiresAI
1103
+ *
1104
+ * Classifies the last user prompt by calling a dedicated classifier AI
1105
+ * Gateway app, stores the result on `AIGatewaySmartRouter`, and
1106
+ * optionally overwrites completions routing from `modelsByComplexity`.
1107
+ * Classification failures, timeouts, and invalid options fail open: the
1108
+ * original request is forwarded.
1109
+ *
1110
+ * @param request - The ZuploRequest
1111
+ * @param context - The ZuploContext
1112
+ * @param options - The policy options set in policies.json
1113
+ * @param policyName - The name of the policy as set in policies.json
1114
+ * @returns A Request or a Response
1115
+ */
1116
+ export declare function AIGatewaySmartRouterInboundPolicy(
1117
+ request: ZuploRequest,
1118
+ context: ZuploContext,
1119
+ unparsedOptions: unknown,
1120
+ policyName: string
1121
+ ): Promise<ZuploRequest | Response>;
1122
+
1123
+ /**
1124
+ * Options for the Smart Router policy: classify the last user prompt with a dedicated classifier AI Gateway app, then optionally route by complexity.
1125
+ * @public
1126
+ */
1127
+ export declare interface AIGatewaySmartRouterInboundPolicyOptions {
1128
+ classifierAppID: ClassifierAppID;
1129
+ classifierAppApiKey: ClassifierAppAPIKey;
1130
+ classifierModel: ClassifierModel;
1131
+ smartRoutingEnabled?: SmartRoutingEnabled;
1132
+ modelsByComplexity?: ModelsByComplexity;
1133
+ intents?: Intents;
1134
+ classifierPrompt?: ClassifierPrompt;
1135
+ minConfidenceForRouting?: MinimumConfidenceForRouting;
1136
+ classifierTimeoutMs?: ClassifierTimeoutMs;
1137
+ maxPromptChars?: MaxPromptCharacters;
1138
+ }
1139
+
1140
+ /**
1141
+ * Structured classification of the last user prompt.
1142
+ *
1143
+ * @public
1144
+ */
1145
+ export declare interface AIGatewaySmartRouterProfile {
1146
+ intent: string;
1147
+ complexity: AIGatewaySmartRouterComplexity;
1148
+ confidence: number;
1149
+ reasons: string[];
1150
+ }
1151
+
1152
+ /**
1153
+ * Where the classified text was taken from on the request.
1154
+ *
1155
+ * @public
1156
+ */
1157
+ export declare type AIGatewaySmartRouterPromptSource = "user" | "prior-user";
1158
+
1159
+ /**
1160
+ * Why Smart Router did or did not change the request's model.
1161
+ *
1162
+ * @public
1163
+ */
1164
+ export declare type AIGatewaySmartRouterReason =
1165
+ | "applied"
1166
+ | "disabled"
1167
+ | "low-confidence"
1168
+ | "no-model"
1169
+ | "internal-error"
1170
+ | "unknown-intent";
1171
+
1172
+ /**
1173
+ * Result produced by the Smart Router policy for the current request.
1174
+ * Custom inbound policies can read this after Smart Router runs.
1175
+ *
1176
+ * @public
1177
+ */
1178
+ export declare interface AIGatewaySmartRouterResult {
1179
+ profile: AIGatewaySmartRouterProfile;
1180
+ usage: AIGatewaySmartRouterTokenUsage;
1181
+ classifierModel: string;
1182
+ routing: AIGatewaySmartRouterRoutingDecision;
1183
+ durationMs: number;
1184
+ promptSource: AIGatewaySmartRouterPromptSource;
1185
+ promptLength: number;
1186
+ promptTruncated: boolean;
1187
+ unknownIntent: boolean;
1188
+ smartRouting: {
1189
+ enabled: boolean;
1190
+ applied: boolean;
1191
+ reason: AIGatewaySmartRouterReason;
1192
+ minConfidence: number;
1193
+ };
1194
+ }
1195
+
1196
+ /**
1197
+ * Model chosen from `modelsByComplexity` for the classified complexity, if any.
1198
+ *
1199
+ * @public
1200
+ */
1201
+ export declare interface AIGatewaySmartRouterRoutingDecision {
1202
+ model?: string;
1203
+ }
1204
+
1205
+ /**
1206
+ * Classifier token usage reported by the classifier chat/completions call.
1207
+ *
1208
+ * @public
1209
+ */
1210
+ export declare interface AIGatewaySmartRouterTokenUsage {
1211
+ promptTokens: number;
1212
+ completionTokens: number;
1213
+ totalTokens: number;
1214
+ }
1215
+
1068
1216
  /**
1069
1217
  * The options for this policy.
1070
1218
  * @public
@@ -3589,6 +3737,36 @@ declare type Claim = string;
3589
3737
  */
3590
3738
  declare type ClaimCheck = EqualsRule | InRule | StartsWithRule;
3591
3739
 
3740
+ /**
3741
+ * API key sent as `Authorization: Bearer` when invoking the classifier app.
3742
+ * @public
3743
+ */
3744
+ declare type ClassifierAppAPIKey = string;
3745
+
3746
+ /**
3747
+ * The AI Gateway application id whose `/v1/chat/completions` route runs the classifier. The policy calls `/{classifierAppID}/v1/chat/completions` via `context.invokeRoute`.
3748
+ * @public
3749
+ */
3750
+ declare type ClassifierAppID = string;
3751
+
3752
+ /**
3753
+ * The `providerName/model` reference the classifier chat/completions request should use.
3754
+ * @public
3755
+ */
3756
+ declare type ClassifierModel = string;
3757
+
3758
+ /**
3759
+ * System prompt for the classifier. If it includes `{{intents}}`, that placeholder is replaced with the configured intent id/description list. A string or an array of lines (joined with newlines). Omit to use the built-in classifier prompt.
3760
+ * @public
3761
+ */
3762
+ declare type ClassifierPrompt = string | [string, ...string[]];
3763
+
3764
+ /**
3765
+ * How long to wait for the classifier `invokeRoute` call before skipping classification and forwarding the original request.
3766
+ * @public
3767
+ */
3768
+ declare type ClassifierTimeoutMs = number;
3769
+
3592
3770
  /**
3593
3771
  * Removes all headers from the incoming request except for those in the exclude list.
3594
3772
  *
@@ -5778,6 +5956,12 @@ declare interface HeaderCredentialsConfig {
5778
5956
  headerName: string;
5779
5957
  }
5780
5958
 
5959
+ /**
5960
+ * Model used for prompts classified as high complexity.
5961
+ * @public
5962
+ */
5963
+ declare type HighComplexityModel = string;
5964
+
5781
5965
  /**
5782
5966
  * Sets HTTP deprecation headers on the outgoing response following the IETF
5783
5967
  * HTTP Deprecation Header standard. Supports the Deprecation, Sunset, and Link
@@ -7654,6 +7838,35 @@ declare interface InRule {
7654
7838
  in: [string | number | boolean, ...(string | number | boolean)[]];
7655
7839
  }
7656
7840
 
7841
+ /**
7842
+ * Short description shown to the classifier for this intent.
7843
+ * @public
7844
+ */
7845
+ declare type IntentDescription = string;
7846
+
7847
+ /**
7848
+ * Stable intent identifier written into the classifier JSON schema enum.
7849
+ * @public
7850
+ */
7851
+ declare type IntentID = string;
7852
+
7853
+ /**
7854
+ * Labels the classifier may assign. Used to build the JSON schema enum and injected into `classifierPrompt` at `{{intents}}`. Omit to use the built-in taxonomy (code, summarization, translation, qa, conversation, classification, creative_writing, agentic, document_qa, other).
7855
+ *
7856
+ * @minItems 1
7857
+ * @public
7858
+ */
7859
+ declare type Intents = [
7860
+ {
7861
+ id: IntentID;
7862
+ description: IntentDescription;
7863
+ },
7864
+ ...{
7865
+ id: IntentID;
7866
+ description: IntentDescription;
7867
+ }[],
7868
+ ];
7869
+
7657
7870
  /**
7658
7871
  * Restricts which client IP addresses can call a route using an allow list, a
7659
7872
  * deny list, or both. Entries are individual IP addresses or CIDR ranges, in
@@ -8298,12 +8511,24 @@ declare type LokiTransportVersion = 1 | 2;
8298
8511
 
8299
8512
  /* Excluded from this release type: LookupResult */
8300
8513
 
8514
+ /**
8515
+ * Model used for prompts classified as low complexity.
8516
+ * @public
8517
+ */
8518
+ declare type LowComplexityModel = string;
8519
+
8301
8520
  /**
8302
8521
  * Skip caching entirely when the conversation has more than this many messages. Unset by default (no limit). Set a bound to guard against topic drift: the cache key covers only the last recentMessageCount messages, so the deeper a conversation goes, the more likely a match on recent messages alone ignores earlier context that should change the answer.
8303
8522
  * @public
8304
8523
  */
8305
8524
  declare type MaxConversationLength = number;
8306
8525
 
8526
+ /**
8527
+ * Maximum characters of user prompt sent to the classifier. Longer prompts are truncated.
8528
+ * @public
8529
+ */
8530
+ declare type MaxPromptCharacters = number;
8531
+
8307
8532
  /**
8308
8533
  * Authenticate MCP gateway requests using a gateway-issued OAuth access token,
8309
8534
  * with browser login delegated to Auth0.
@@ -10087,6 +10312,12 @@ export declare interface McpWorkosOAuthInboundPolicyOptions {
10087
10312
  };
10088
10313
  }
10089
10314
 
10315
+ /**
10316
+ * Model used for prompts classified as medium complexity.
10317
+ * @public
10318
+ */
10319
+ declare type MediumComplexityModel = string;
10320
+
10090
10321
  declare interface MemoryCacheOptions {
10091
10322
  maxSize: number;
10092
10323
  }
@@ -10270,6 +10501,12 @@ declare interface MetricsType {
10270
10501
  responseContentLength?: boolean;
10271
10502
  }
10272
10503
 
10504
+ /**
10505
+ * Minimum confidence (0–1) required before applying model routing. Unknown intents are capped strictly below this threshold.
10506
+ * @public
10507
+ */
10508
+ declare type MinimumConfidenceForRouting = number;
10509
+
10273
10510
  /**
10274
10511
  * The options for this policy.
10275
10512
  * @public
@@ -10322,6 +10559,16 @@ declare interface Models {
10322
10559
  embeddings?: EmbeddingsModels;
10323
10560
  }
10324
10561
 
10562
+ /**
10563
+ * Optional `providerName/model` routing by classified complexity. Used only when `smartRoutingEnabled` is true.
10564
+ * @public
10565
+ */
10566
+ declare interface ModelsByComplexity {
10567
+ low?: LowComplexityModel;
10568
+ medium?: MediumComplexityModel;
10569
+ high?: HighComplexityModel;
10570
+ }
10571
+
10325
10572
  declare type Modify<T, R> = Omit<T, keyof R> & R;
10326
10573
 
10327
10574
  declare interface MoesifContext {
@@ -13907,6 +14154,12 @@ export declare interface SleepInboundPolicyOptions {
13907
14154
  sleepInMs: number;
13908
14155
  }
13909
14156
 
14157
+ /**
14158
+ * When true, apply model routing from `modelsByComplexity` when confidence is high enough and the intent is known. Classification still runs when false.
14159
+ * @public
14160
+ */
14161
+ declare type SmartRoutingEnabled = boolean;
14162
+
13910
14163
  declare interface SplunkLoggingOptions {
13911
14164
  /**
13912
14165
  * The URL for the Splunk HTTP Event Collector (HEC) endpoint.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zuplo/runtime",
3
3
  "type": "module",
4
- "version": "7.5.0",
4
+ "version": "7.5.2",
5
5
  "repository": "https://github.com/zuplo/zuplo",
6
6
  "author": "Zuplo, Inc.",
7
7
  "exports": {
@@ -206,7 +206,7 @@ const saveContentToFile = async (data, fsModule, outDir, extensionMap) => {
206
206
  const { routePath, content, mimeType } = awaitedData;
207
207
  const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap);
208
208
  const dirPath = (0, import_utils2.dirname)(filePath);
209
- if (!createdDirs.has(dirPath)) {
209
+ if (dirPath !== "" && !createdDirs.has(dirPath)) {
210
210
  await fsModule.mkdir(dirPath, { recursive: true });
211
211
  createdDirs.add(dirPath);
212
212
  }
@@ -33,8 +33,14 @@ const dirname = (path) => {
33
33
  const normalizePath = (path) => {
34
34
  return path.replace(/(\\)/g, "/").replace(/\/$/g, "");
35
35
  };
36
- const handleParent = (resultPaths, beforeParentFlag) => {
37
- if (resultPaths.length === 0 || beforeParentFlag) {
36
+ const getUncRoot = (path) => {
37
+ const uncRoot = path.replace(/\\/g, "/").match(/^\/\/([^/]+)\/([^/]+)/);
38
+ if (uncRoot) {
39
+ return `${uncRoot[1].toLowerCase()}/${uncRoot[2].toLowerCase()}`;
40
+ }
41
+ };
42
+ const handleParent = (resultPaths) => {
43
+ if (resultPaths.length === 0 || resultPaths[resultPaths.length - 1] === "..") {
38
44
  resultPaths.push("..");
39
45
  } else {
40
46
  resultPaths.pop();
@@ -47,22 +53,20 @@ const handleNonDot = (path, resultPaths) => {
47
53
  }
48
54
  };
49
55
  const handleSegments = (paths, resultPaths) => {
50
- let beforeParentFlag = false;
51
56
  for (const path of paths) {
52
57
  if (path === "..") {
53
- handleParent(resultPaths, beforeParentFlag);
54
- beforeParentFlag = true;
58
+ handleParent(resultPaths);
55
59
  } else {
56
60
  handleNonDot(path, resultPaths);
57
- beforeParentFlag = false;
58
61
  }
59
62
  }
60
63
  };
61
64
  const joinPaths = (...paths) => {
65
+ const hasUncPrefix = getUncRoot(paths[0]) !== void 0;
62
66
  paths = paths.map(normalizePath);
63
67
  const resultPaths = [];
64
68
  handleSegments(paths.join("/").split("/"), resultPaths);
65
- return (paths[0][0] === "/" ? "/" : "") + resultPaths.join("/");
69
+ return (hasUncPrefix ? "//" : paths[0][0] === "/" ? "/" : "") + resultPaths.join("/");
66
70
  };
67
71
  const filterStaticGenerateRoutes = (hono) => {
68
72
  return hono.routes.reduce((acc, { method, handler, path }) => {
@@ -76,10 +80,26 @@ const filterStaticGenerateRoutes = (hono) => {
76
80
  const isDynamicRoute = (path) => {
77
81
  return path.split("/").some((segment) => segment.startsWith(":") || segment.includes("*"));
78
82
  };
83
+ const toSegments = (path) => path === "" ? [] : path.split("/");
84
+ const getPathRoot = (path) => {
85
+ const normalizedPath = path.replace(/\\/g, "/");
86
+ const uncRoot = getUncRoot(normalizedPath);
87
+ if (uncRoot) {
88
+ return `unc:${uncRoot}`;
89
+ }
90
+ const driveRoot = normalizedPath.match(/^([A-Za-z]):/);
91
+ if (driveRoot) {
92
+ const kind = normalizedPath[2] === "/" ? "drive-absolute" : "drive-relative";
93
+ return `${kind}:${driveRoot[1].toLowerCase()}`;
94
+ }
95
+ return normalizedPath.startsWith("/") ? "absolute" : "relative";
96
+ };
79
97
  const ensureWithinOutDir = (outDir, filePath) => {
80
- const normalizedOutDir = joinPaths("/", outDir);
81
- const normalizedFilePath = joinPaths("/", filePath);
82
- if (normalizedFilePath !== normalizedOutDir && !normalizedFilePath.startsWith(`${normalizedOutDir}/`)) {
98
+ const outDirSegments = toSegments(joinPaths(outDir));
99
+ const filePathSegments = toSegments(joinPaths(filePath));
100
+ const hasMismatchedPathRoot = getPathRoot(outDir) !== getPathRoot(filePath);
101
+ const climbsAboveOutDir = filePathSegments[outDirSegments.length] === "..";
102
+ if (hasMismatchedPathRoot || filePathSegments.length <= outDirSegments.length || !outDirSegments.every((segment, i) => segment === filePathSegments[i]) || climbsAboveOutDir) {
83
103
  throw new Error(`Path traversal detected: "${filePath}" is outside the output directory`);
84
104
  }
85
105
  };
@@ -44,7 +44,7 @@ const parseVaryDirectives = (vary) => {
44
44
  };
45
45
  const createCacheKey = (key, requestUrl, request, varyHeaders) => {
46
46
  const url = new URL(cacheKeyPath, requestUrl);
47
- url.searchParams.append(cacheKeyParameter, key.split("#", 1)[0]);
47
+ url.searchParams.append(cacheKeyParameter, key);
48
48
  url.searchParams.append(cacheMethodKeyParameter, request.method);
49
49
  if (request.method === "QUERY") {
50
50
  url.searchParams.append(queryDigestKeyParameter, request.digest);
@@ -21,6 +21,8 @@ __export(body_exports, {
21
21
  });
22
22
  module.exports = __toCommonJS(body_exports);
23
23
  var import_buffer = require("./buffer");
24
+ const MAX_NESTING_DEPTH = 32;
25
+ const MAX_NESTED_OBJECTS = 1e4;
24
26
  const isRawRequest = (request) => "headers" in request;
25
27
  const parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
26
28
  const { all = false, dot = false } = options;
@@ -53,6 +55,7 @@ async function parseFormData(request, options) {
53
55
  }
54
56
  function convertFormDataToBodyData(formData, options) {
55
57
  const form = /* @__PURE__ */ Object.create(null);
58
+ const nestingState = { count: 0 };
56
59
  formData.forEach((value, key) => {
57
60
  const shouldParseAllValues = options.all || key.endsWith("[]");
58
61
  if (!shouldParseAllValues) {
@@ -65,7 +68,7 @@ function convertFormDataToBodyData(formData, options) {
65
68
  Object.entries(form).forEach(([key, value]) => {
66
69
  const shouldParseDotValues = key.includes(".");
67
70
  if (shouldParseDotValues) {
68
- handleParsingNestedValues(form, key, value);
71
+ handleParsingNestedValues(form, key, value, nestingState);
69
72
  delete form[key];
70
73
  }
71
74
  });
@@ -88,23 +91,32 @@ const handleParsingAllValues = (form, key, value) => {
88
91
  }
89
92
  }
90
93
  };
91
- const handleParsingNestedValues = (form, key, value) => {
94
+ const handleParsingNestedValues = (form, key, value, state) => {
92
95
  if (/(?:^|\.)__proto__\./.test(key)) {
93
96
  return;
94
97
  }
95
98
  let nestedForm = form;
96
- const keys = key.split(".");
99
+ const keys = key.split(".", MAX_NESTING_DEPTH + 2);
100
+ if (keys.length > MAX_NESTING_DEPTH + 1) {
101
+ throwNestingLimitExceeded();
102
+ }
97
103
  keys.forEach((key2, index) => {
98
104
  if (index === keys.length - 1) {
99
105
  nestedForm[key2] = value;
100
106
  } else {
101
107
  if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
108
+ if (state.count++ >= MAX_NESTED_OBJECTS) {
109
+ throwNestingLimitExceeded();
110
+ }
102
111
  nestedForm[key2] = /* @__PURE__ */ Object.create(null);
103
112
  }
104
113
  nestedForm = nestedForm[key2];
105
114
  }
106
115
  });
107
116
  };
117
+ const throwNestingLimitExceeded = () => {
118
+ throw new Error("Nesting limit exceeded");
119
+ };
108
120
  // Annotate the CommonJS export names for ESM import in node:
109
121
  0 && (module.exports = {
110
122
  parseBody
@@ -119,7 +119,11 @@ const getPath = (request) => {
119
119
  };
120
120
  const getQueryStrings = (url) => {
121
121
  const queryIndex = url.indexOf("?", 8);
122
- return queryIndex === -1 ? "" : "?" + url.slice(queryIndex + 1);
122
+ if (queryIndex === -1) {
123
+ return "";
124
+ }
125
+ const hashIndex = url.indexOf("#", 8);
126
+ return hashIndex === -1 ? url.slice(queryIndex) : queryIndex < hashIndex ? url.slice(queryIndex, hashIndex) : "";
123
127
  };
124
128
  const getPathNoStrict = (request) => {
125
129
  const result = getPath(request);
@@ -166,6 +170,10 @@ const _decodeURI = (value) => {
166
170
  return tryDecodeURIComponent(value);
167
171
  };
168
172
  const _getQueryParam = (url, key, multiple) => {
173
+ const hashIndex = url.indexOf("#", 8);
174
+ if (hashIndex !== -1) {
175
+ url = url.slice(0, hashIndex);
176
+ }
169
177
  let encoded;
170
178
  if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
171
179
  let keyIndex2 = url.indexOf("?", 8);
@@ -184,7 +184,7 @@ var saveContentToFile = async (data, fsModule, outDir, extensionMap) => {
184
184
  const { routePath, content, mimeType } = awaitedData;
185
185
  const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap);
186
186
  const dirPath = dirname(filePath);
187
- if (!createdDirs.has(dirPath)) {
187
+ if (dirPath !== "" && !createdDirs.has(dirPath)) {
188
188
  await fsModule.mkdir(dirPath, { recursive: true });
189
189
  createdDirs.add(dirPath);
190
190
  }
@@ -8,8 +8,14 @@ var dirname = (path) => {
8
8
  var normalizePath = (path) => {
9
9
  return path.replace(/(\\)/g, "/").replace(/\/$/g, "");
10
10
  };
11
- var handleParent = (resultPaths, beforeParentFlag) => {
12
- if (resultPaths.length === 0 || beforeParentFlag) {
11
+ var getUncRoot = (path) => {
12
+ const uncRoot = path.replace(/\\/g, "/").match(/^\/\/([^/]+)\/([^/]+)/);
13
+ if (uncRoot) {
14
+ return `${uncRoot[1].toLowerCase()}/${uncRoot[2].toLowerCase()}`;
15
+ }
16
+ };
17
+ var handleParent = (resultPaths) => {
18
+ if (resultPaths.length === 0 || resultPaths[resultPaths.length - 1] === "..") {
13
19
  resultPaths.push("..");
14
20
  } else {
15
21
  resultPaths.pop();
@@ -22,22 +28,20 @@ var handleNonDot = (path, resultPaths) => {
22
28
  }
23
29
  };
24
30
  var handleSegments = (paths, resultPaths) => {
25
- let beforeParentFlag = false;
26
31
  for (const path of paths) {
27
32
  if (path === "..") {
28
- handleParent(resultPaths, beforeParentFlag);
29
- beforeParentFlag = true;
33
+ handleParent(resultPaths);
30
34
  } else {
31
35
  handleNonDot(path, resultPaths);
32
- beforeParentFlag = false;
33
36
  }
34
37
  }
35
38
  };
36
39
  var joinPaths = (...paths) => {
40
+ const hasUncPrefix = getUncRoot(paths[0]) !== void 0;
37
41
  paths = paths.map(normalizePath);
38
42
  const resultPaths = [];
39
43
  handleSegments(paths.join("/").split("/"), resultPaths);
40
- return (paths[0][0] === "/" ? "/" : "") + resultPaths.join("/");
44
+ return (hasUncPrefix ? "//" : paths[0][0] === "/" ? "/" : "") + resultPaths.join("/");
41
45
  };
42
46
  var filterStaticGenerateRoutes = (hono) => {
43
47
  return hono.routes.reduce((acc, { method, handler, path }) => {
@@ -51,10 +55,26 @@ var filterStaticGenerateRoutes = (hono) => {
51
55
  var isDynamicRoute = (path) => {
52
56
  return path.split("/").some((segment) => segment.startsWith(":") || segment.includes("*"));
53
57
  };
58
+ var toSegments = (path) => path === "" ? [] : path.split("/");
59
+ var getPathRoot = (path) => {
60
+ const normalizedPath = path.replace(/\\/g, "/");
61
+ const uncRoot = getUncRoot(normalizedPath);
62
+ if (uncRoot) {
63
+ return `unc:${uncRoot}`;
64
+ }
65
+ const driveRoot = normalizedPath.match(/^([A-Za-z]):/);
66
+ if (driveRoot) {
67
+ const kind = normalizedPath[2] === "/" ? "drive-absolute" : "drive-relative";
68
+ return `${kind}:${driveRoot[1].toLowerCase()}`;
69
+ }
70
+ return normalizedPath.startsWith("/") ? "absolute" : "relative";
71
+ };
54
72
  var ensureWithinOutDir = (outDir, filePath) => {
55
- const normalizedOutDir = joinPaths("/", outDir);
56
- const normalizedFilePath = joinPaths("/", filePath);
57
- if (normalizedFilePath !== normalizedOutDir && !normalizedFilePath.startsWith(`${normalizedOutDir}/`)) {
73
+ const outDirSegments = toSegments(joinPaths(outDir));
74
+ const filePathSegments = toSegments(joinPaths(filePath));
75
+ const hasMismatchedPathRoot = getPathRoot(outDir) !== getPathRoot(filePath);
76
+ const climbsAboveOutDir = filePathSegments[outDirSegments.length] === "..";
77
+ if (hasMismatchedPathRoot || filePathSegments.length <= outDirSegments.length || !outDirSegments.every((segment, i) => segment === filePathSegments[i]) || climbsAboveOutDir) {
58
78
  throw new Error(`Path traversal detected: "${filePath}" is outside the output directory`);
59
79
  }
60
80
  };
@@ -23,7 +23,7 @@ var parseVaryDirectives = (vary) => {
23
23
  };
24
24
  var createCacheKey = (key, requestUrl, request, varyHeaders) => {
25
25
  const url = new URL(cacheKeyPath, requestUrl);
26
- url.searchParams.append(cacheKeyParameter, key.split("#", 1)[0]);
26
+ url.searchParams.append(cacheKeyParameter, key);
27
27
  url.searchParams.append(cacheMethodKeyParameter, request.method);
28
28
  if (request.method === "QUERY") {
29
29
  url.searchParams.append(queryDigestKeyParameter, request.digest);
@@ -19,6 +19,10 @@ export declare const tryDecode: (str: string, decoder: Decoder) => string;
19
19
  */
20
20
  export declare const tryDecodeURI: (str: string) => string;
21
21
  export declare const getPath: (request: Request) => string;
22
+ /**
23
+ * @deprecated
24
+ * Use the `URL` API instead.
25
+ */
22
26
  export declare const getQueryStrings: (url: string) => string;
23
27
  export declare const getPathNoStrict: (request: Request) => string;
24
28
  /**
@@ -1,5 +1,7 @@
1
1
  // src/utils/body.ts
2
2
  import { bufferToFormData } from "./buffer.js";
3
+ var MAX_NESTING_DEPTH = 32;
4
+ var MAX_NESTED_OBJECTS = 1e4;
3
5
  var isRawRequest = (request) => "headers" in request;
4
6
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
5
7
  const { all = false, dot = false } = options;
@@ -32,6 +34,7 @@ async function parseFormData(request, options) {
32
34
  }
33
35
  function convertFormDataToBodyData(formData, options) {
34
36
  const form = /* @__PURE__ */ Object.create(null);
37
+ const nestingState = { count: 0 };
35
38
  formData.forEach((value, key) => {
36
39
  const shouldParseAllValues = options.all || key.endsWith("[]");
37
40
  if (!shouldParseAllValues) {
@@ -44,7 +47,7 @@ function convertFormDataToBodyData(formData, options) {
44
47
  Object.entries(form).forEach(([key, value]) => {
45
48
  const shouldParseDotValues = key.includes(".");
46
49
  if (shouldParseDotValues) {
47
- handleParsingNestedValues(form, key, value);
50
+ handleParsingNestedValues(form, key, value, nestingState);
48
51
  delete form[key];
49
52
  }
50
53
  });
@@ -67,23 +70,32 @@ var handleParsingAllValues = (form, key, value) => {
67
70
  }
68
71
  }
69
72
  };
70
- var handleParsingNestedValues = (form, key, value) => {
73
+ var handleParsingNestedValues = (form, key, value, state) => {
71
74
  if (/(?:^|\.)__proto__\./.test(key)) {
72
75
  return;
73
76
  }
74
77
  let nestedForm = form;
75
- const keys = key.split(".");
78
+ const keys = key.split(".", MAX_NESTING_DEPTH + 2);
79
+ if (keys.length > MAX_NESTING_DEPTH + 1) {
80
+ throwNestingLimitExceeded();
81
+ }
76
82
  keys.forEach((key2, index) => {
77
83
  if (index === keys.length - 1) {
78
84
  nestedForm[key2] = value;
79
85
  } else {
80
86
  if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
87
+ if (state.count++ >= MAX_NESTED_OBJECTS) {
88
+ throwNestingLimitExceeded();
89
+ }
81
90
  nestedForm[key2] = /* @__PURE__ */ Object.create(null);
82
91
  }
83
92
  nestedForm = nestedForm[key2];
84
93
  }
85
94
  });
86
95
  };
96
+ var throwNestingLimitExceeded = () => {
97
+ throw new Error("Nesting limit exceeded");
98
+ };
87
99
  export {
88
100
  parseBody
89
101
  };