@zuplo/cli 6.72.17 → 6.73.0

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.
@@ -615,6 +615,13 @@ export declare interface AIGatewaySemanticCacheOutboundPolicyOptions {}
615
615
  * route's chain is what enables it. `namespace` defaults to the AI Gateway
616
616
  * configuration id when omitted so caching stays scoped per tenant.
617
617
  *
618
+ * Cache outcomes are reported on the response via the RFC 9211 `Cache-Status`
619
+ * header under the cache name `zp-aigw-sem-cache` (hit:
620
+ * `zp-aigw-sem-cache; hit; detail="similarity=0.93"`; miss:
621
+ * `zp-aigw-sem-cache; fwd=miss; stored`) alongside the legacy
622
+ * `x-ai-gateway-cache: HIT|MISS` and `x-ai-gateway-cache-similarity` headers,
623
+ * kept for compatibility.
624
+ *
618
625
  * @title AI Gateway Semantic Cache (v2)
619
626
  * @product ai-gateway
620
627
  * @hidden
@@ -2673,7 +2680,7 @@ declare interface CacheOptions {
2673
2680
  }
2674
2681
 
2675
2682
  /**
2676
- * How long a cached response lives, in seconds. Defaults to 3600 (1 hour).
2683
+ * How long a cached response lives, in whole seconds. Defaults to 3600 (1 hour); maximum 2592000 (30 days).
2677
2684
  * @public
2678
2685
  */
2679
2686
  declare type CacheTTLSeconds = number;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zuplo/runtime",
3
3
  "type": "module",
4
- "version": "6.72.17",
4
+ "version": "6.73.0",
5
5
  "repository": "https://github.com/zuplo/zuplo",
6
6
  "author": "Zuplo, Inc.",
7
7
  "exports": {
@@ -202,6 +202,10 @@ function serialize (cmpts, opts) {
202
202
 
203
203
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
204
204
 
205
+ // Captures the authority component (between "//" and the next "/", "?" or "#"),
206
+ // with or without a scheme prefix, for the literal-backslash rejection below.
207
+ const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
208
+
205
209
  /**
206
210
  * @param {import('./types/index').URIComponent} parsed
207
211
  * @param {RegExpMatchArray} matches
@@ -248,6 +252,19 @@ function parseWithStatus (uri, opts) {
248
252
  }
249
253
  }
250
254
 
255
+ // A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
256
+ // not an authority delimiter. Reject it in the authority rather than
257
+ // rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
258
+ // change the resource identified by an otherwise-invalid input, and lets "\"
259
+ // act as a host delimiter here while Node's native URL parses a different
260
+ // host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
261
+ // untouched and remains valid encoded data.
262
+ const authorityMatch = uri.match(AUTHORITY_PREFIX)
263
+ if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
264
+ parsed.error = 'URI authority must not contain a literal backslash.'
265
+ malformedAuthorityOrPort = true
266
+ }
267
+
251
268
  const matches = uri.match(URI_PARSE)
252
269
 
253
270
  if (matches) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fast-uri",
3
3
  "description": "Dependency-free RFC 3986 URI toolbox",
4
- "version": "3.1.3",
4
+ "version": "3.1.4",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
7
7
  "types": "types/index.d.ts",
@@ -159,3 +159,65 @@ test('parse canonicalises IDN / Unicode hosts to their ASCII form', (t) => {
159
159
  t.equal(parsed.host, expectedHost, `host canonicalised to ASCII: ${description}`)
160
160
  })
161
161
  })
162
+
163
+ test('parse rejects a literal backslash in the authority as malformed (RFC 3986)', (t) => {
164
+ // Regression for the host-confusion bypass: a literal "\" is invalid RFC 3986
165
+ // syntax and must be flagged malformed, not silently rewritten. Otherwise "\"
166
+ // acts as a host delimiter here while Node's native URL parses a different
167
+ // host, defeating a host-based SSRF/redirect/origin allowlist.
168
+ const cases = [
169
+ 'http://evil.com\\@allowed.com',
170
+ 'https://169.254.169.254\\@trusted.example.com',
171
+ 'http://127.0.0.1\\@public.example.com',
172
+ 'https://attacker.com\\@api.internal',
173
+ 'http://a\\@b',
174
+ 'ws://evil.com\\@allowed.com/chat',
175
+ 'wss://evil.com\\@allowed.com/chat',
176
+ 'http://evil.com\\%40allowed.com',
177
+ '//evil.com\\@allowed.com'
178
+ ]
179
+
180
+ t.plan(cases.length)
181
+
182
+ cases.forEach((input) => {
183
+ t.equal(
184
+ fastURI.parse(input).error,
185
+ 'URI authority must not contain a literal backslash.',
186
+ input
187
+ )
188
+ })
189
+ })
190
+
191
+ test('normalize does not canonicalize a literal-backslash URI into a different valid URL', (t) => {
192
+ const cases = [
193
+ 'http://evil.com\\@allowed.com',
194
+ 'https://attacker.com\\@api.internal'
195
+ ]
196
+
197
+ t.plan(cases.length)
198
+
199
+ cases.forEach((input) => {
200
+ t.equal(fastURI.normalize(input), input, input)
201
+ })
202
+ })
203
+
204
+ test('parse leaves percent-encoded %5C untouched as encoded data (not rejected)', (t) => {
205
+ // Only the literal "\" byte is rejected; %5C stays valid encoded data and
206
+ // does not diverge from the native URL parser, so it must not be flagged.
207
+ const input = 'http://evil.com%5C@allowed.com'
208
+ const parsed = fastURI.parse(input)
209
+
210
+ t.plan(2)
211
+ t.notOk(parsed.error, '%5C is valid encoded data, not malformed')
212
+ t.equal(parsed.host, new URL(input).hostname, '%5C host matches native URL (no divergence)')
213
+ })
214
+
215
+ test('parse does not reject a literal backslash in the query or fragment', (t) => {
216
+ // The rejection is scoped to the authority/path (the host-confusion surface);
217
+ // a backslash after "?"/"#" is normalized as encoded data as before.
218
+ const parsed = fastURI.parse('http://host.example.com/?x=\\y#z\\w')
219
+
220
+ t.plan(2)
221
+ t.notOk(parsed.error, 'backslash in query/fragment does not mark the URI malformed')
222
+ t.equal(parsed.host, 'host.example.com', 'host parsed normally')
223
+ })
@@ -33,8 +33,9 @@ class SSEStreamingApi extends import_stream.StreamingApi {
33
33
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
34
34
  return `data: ${line}`;
35
35
  }).join("\n");
36
- for (const key of ["event", "id", "retry"]) {
37
- if (message[key] && /[\r\n]/.test(message[key])) {
36
+ for (const key of ["event", "id"]) {
37
+ const value = message[key];
38
+ if (value && /[\r\n]/.test(value)) {
38
39
  throw new Error(`${key} must not contain "\\r" or "\\n"`);
39
40
  }
40
41
  }
@@ -42,7 +43,7 @@ class SSEStreamingApi extends import_stream.StreamingApi {
42
43
  message.event && `event: ${message.event}`,
43
44
  dataLines,
44
45
  message.id && `id: ${message.id}`,
45
- message.retry && `retry: ${message.retry}`
46
+ message.retry !== void 0 && `retry: ${message.retry}`
46
47
  ].filter(Boolean).join("\n") + "\n\n";
47
48
  await this.write(sseData);
48
49
  }
@@ -316,11 +316,16 @@ const cloneRawRequest = async (req) => {
316
316
  message: "Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly."
317
317
  });
318
318
  }
319
+ const body = await req[cacheKey]();
320
+ const headers = req.header();
321
+ if (body instanceof FormData) {
322
+ delete headers["content-type"];
323
+ }
319
324
  const requestInit = {
320
- body: await req[cacheKey](),
325
+ body,
321
326
  cache: req.raw.cache,
322
327
  credentials: req.raw.credentials,
323
- headers: req.header(),
328
+ headers,
324
329
  integrity: req.raw.integrity,
325
330
  keepalive: req.raw.keepalive,
326
331
  method: req.method,
@@ -33,6 +33,12 @@ const parseBody = async (request, options = /* @__PURE__ */ Object.create(null))
33
33
  return {};
34
34
  };
35
35
  async function parseFormData(request, options) {
36
+ if (!isRawRequest(request) && request.bodyCache.formData) {
37
+ return convertFormDataToBodyData(
38
+ await request.bodyCache.formData,
39
+ options
40
+ );
41
+ }
36
42
  const headers = isRawRequest(request) ? request.headers : request.raw.headers;
37
43
  const arrayBuffer = await request.arrayBuffer();
38
44
  const formDataPromise = (0, import_buffer.bufferToFormData)(arrayBuffer, headers.get("Content-Type") || "");
@@ -11,8 +11,9 @@ var SSEStreamingApi = class extends StreamingApi {
11
11
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
12
12
  return `data: ${line}`;
13
13
  }).join("\n");
14
- for (const key of ["event", "id", "retry"]) {
15
- if (message[key] && /[\r\n]/.test(message[key])) {
14
+ for (const key of ["event", "id"]) {
15
+ const value = message[key];
16
+ if (value && /[\r\n]/.test(value)) {
16
17
  throw new Error(`${key} must not contain "\\r" or "\\n"`);
17
18
  }
18
19
  }
@@ -20,7 +21,7 @@ var SSEStreamingApi = class extends StreamingApi {
20
21
  message.event && `event: ${message.event}`,
21
22
  dataLines,
22
23
  message.id && `id: ${message.id}`,
23
- message.retry && `retry: ${message.retry}`
24
+ message.retry !== void 0 && `retry: ${message.retry}`
24
25
  ].filter(Boolean).join("\n") + "\n\n";
25
26
  await this.write(sseData);
26
27
  }
@@ -294,11 +294,16 @@ var cloneRawRequest = async (req) => {
294
294
  message: "Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly."
295
295
  });
296
296
  }
297
+ const body = await req[cacheKey]();
298
+ const headers = req.header();
299
+ if (body instanceof FormData) {
300
+ delete headers["content-type"];
301
+ }
297
302
  const requestInit = {
298
- body: await req[cacheKey](),
303
+ body,
299
304
  cache: req.raw.cache,
300
305
  credentials: req.raw.credentials,
301
- headers: req.header(),
306
+ headers,
302
307
  integrity: req.raw.integrity,
303
308
  keepalive: req.raw.keepalive,
304
309
  method: req.method,
@@ -71,7 +71,7 @@ export declare const every: (...middleware: (MiddlewareHandler | Condition)[]) =
71
71
  * @example
72
72
  * ```ts
73
73
  * import { except } from 'hono/combine'
74
- * import { bearerAuth } from 'hono/bearer-auth
74
+ * import { bearerAuth } from 'hono/bearer-auth'
75
75
  *
76
76
  * // If client is accessing public API, then skip authentication.
77
77
  * // Otherwise, require a valid token.
@@ -12,6 +12,12 @@ var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) =
12
12
  return {};
13
13
  };
14
14
  async function parseFormData(request, options) {
15
+ if (!isRawRequest(request) && request.bodyCache.formData) {
16
+ return convertFormDataToBodyData(
17
+ await request.bodyCache.formData,
18
+ options
19
+ );
20
+ }
15
21
  const headers = isRawRequest(request) ? request.headers : request.raw.headers;
16
22
  const arrayBuffer = await request.arrayBuffer();
17
23
  const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "4.12.30",
3
+ "version": "4.12.31",
4
4
  "description": "Web framework built on Web Standards",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuplo/cli",
3
- "version": "6.72.17",
3
+ "version": "6.73.0",
4
4
  "repository": "https://github.com/zuplo/zuplo",
5
5
  "author": "Zuplo, Inc.",
6
6
  "type": "module",
@@ -27,10 +27,10 @@
27
27
  "@opentelemetry/api": "1.9.0",
28
28
  "@opentelemetry/api-logs": "0.201.1",
29
29
  "@swc/core": "1.10.18",
30
- "@zuplo/core": "6.72.17",
30
+ "@zuplo/core": "6.73.0",
31
31
  "@zuplo/editor": "1.0.20821740935",
32
- "@zuplo/openapi-tools": "6.72.17",
33
- "@zuplo/runtime": "6.72.17",
32
+ "@zuplo/openapi-tools": "6.73.0",
33
+ "@zuplo/runtime": "6.73.0",
34
34
  "chalk": "5.4.1",
35
35
  "chokidar": "3.5.3",
36
36
  "cookie": "1.0.2",
@@ -61,8 +61,8 @@
61
61
  "workerd": "1.20241230.0",
62
62
  "yargs": "17.7.2",
63
63
  "zod": "3.25.76",
64
- "@zuplo/graphql": "6.72.17",
65
- "@zuplo/otel": "6.72.17"
64
+ "@zuplo/graphql": "6.73.0",
65
+ "@zuplo/otel": "6.73.0"
66
66
  },
67
67
  "bundleDependencies": [
68
68
  "@inquirer/prompts",