hono 4.9.2 → 4.9.3

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.
@@ -62,13 +62,13 @@ const preprocessRequestInit = (requestInit) => {
62
62
  return requestInit;
63
63
  };
64
64
  const proxy = async (input, proxyInit) => {
65
- const { raw, ...requestInit } = proxyInit instanceof Request ? { raw: proxyInit } : proxyInit ?? {};
65
+ const { raw, customFetch, ...requestInit } = proxyInit instanceof Request ? { raw: proxyInit } : proxyInit ?? {};
66
66
  const req = new Request(input, {
67
67
  ...buildRequestInitFromRequest(raw),
68
68
  ...preprocessRequestInit(requestInit)
69
69
  });
70
70
  req.headers.delete("accept-encoding");
71
- const res = await fetch(req);
71
+ const res = await (customFetch || fetch)(req);
72
72
  const resHeaders = new Headers(res.headers);
73
73
  hopByHopHeaders.forEach((header) => {
74
74
  resHeaders.delete(header);
@@ -22,10 +22,12 @@ __export(csrf_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(csrf_exports);
24
24
  var import_http_exception = require("../../http-exception");
25
+ const secFetchSiteValues = ["same-origin", "same-site", "none", "cross-site"];
26
+ const isSecFetchSite = (value) => secFetchSiteValues.includes(value);
25
27
  const isSafeMethodRe = /^(GET|HEAD)$/;
26
28
  const isRequestedByFormElementRe = /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/i;
27
29
  const csrf = (options) => {
28
- const handler = ((optsOrigin) => {
30
+ const originHandler = ((optsOrigin) => {
29
31
  if (!optsOrigin) {
30
32
  return (origin, c) => origin === new URL(c.req.url).origin;
31
33
  } else if (typeof optsOrigin === "string") {
@@ -40,13 +42,31 @@ const csrf = (options) => {
40
42
  if (origin === void 0) {
41
43
  return false;
42
44
  }
43
- return handler(origin, c);
45
+ return originHandler(origin, c);
46
+ };
47
+ const secFetchSiteHandler = ((optsSecFetchSite) => {
48
+ if (!optsSecFetchSite) {
49
+ return (secFetchSite) => secFetchSite === "same-origin";
50
+ } else if (typeof optsSecFetchSite === "string") {
51
+ return (secFetchSite) => secFetchSite === optsSecFetchSite;
52
+ } else if (typeof optsSecFetchSite === "function") {
53
+ return optsSecFetchSite;
54
+ } else {
55
+ return (secFetchSite) => optsSecFetchSite.includes(secFetchSite);
56
+ }
57
+ })(options?.secFetchSite);
58
+ const isAllowedSecFetchSite = (secFetchSite, c) => {
59
+ if (secFetchSite === void 0) {
60
+ return false;
61
+ }
62
+ if (!isSecFetchSite(secFetchSite)) {
63
+ return false;
64
+ }
65
+ return secFetchSiteHandler(secFetchSite, c);
44
66
  };
45
67
  return async function csrf2(c, next) {
46
- if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedOrigin(c.req.header("origin"), c)) {
47
- const res = new Response("Forbidden", {
48
- status: 403
49
- });
68
+ if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedSecFetchSite(c.req.header("sec-fetch-site"), c) && !isAllowedOrigin(c.req.header("origin"), c)) {
69
+ const res = new Response("Forbidden", { status: 403 });
50
70
  throw new import_http_exception.HTTPException(403, { res });
51
71
  }
52
72
  await next();
@@ -25,7 +25,9 @@ const mergeBuffers = (buffer1, buffer2) => {
25
25
  if (!buffer1) {
26
26
  return buffer2;
27
27
  }
28
- const merged = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
28
+ const merged = new Uint8Array(
29
+ new ArrayBuffer(buffer1.byteLength + buffer2.byteLength)
30
+ );
29
31
  merged.set(new Uint8Array(buffer1), 0);
30
32
  merged.set(buffer2, buffer1.byteLength);
31
33
  return merged;
@@ -40,13 +40,13 @@ var preprocessRequestInit = (requestInit) => {
40
40
  return requestInit;
41
41
  };
42
42
  var proxy = async (input, proxyInit) => {
43
- const { raw, ...requestInit } = proxyInit instanceof Request ? { raw: proxyInit } : proxyInit ?? {};
43
+ const { raw, customFetch, ...requestInit } = proxyInit instanceof Request ? { raw: proxyInit } : proxyInit ?? {};
44
44
  const req = new Request(input, {
45
45
  ...buildRequestInitFromRequest(raw),
46
46
  ...preprocessRequestInit(requestInit)
47
47
  });
48
48
  req.headers.delete("accept-encoding");
49
- const res = await fetch(req);
49
+ const res = await (customFetch || fetch)(req);
50
50
  const resHeaders = new Headers(res.headers);
51
51
  hopByHopHeaders.forEach((header) => {
52
52
  resHeaders.delete(header);
@@ -1,9 +1,11 @@
1
1
  // src/middleware/csrf/index.ts
2
2
  import { HTTPException } from "../../http-exception.js";
3
+ var secFetchSiteValues = ["same-origin", "same-site", "none", "cross-site"];
4
+ var isSecFetchSite = (value) => secFetchSiteValues.includes(value);
3
5
  var isSafeMethodRe = /^(GET|HEAD)$/;
4
6
  var isRequestedByFormElementRe = /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/i;
5
7
  var csrf = (options) => {
6
- const handler = ((optsOrigin) => {
8
+ const originHandler = ((optsOrigin) => {
7
9
  if (!optsOrigin) {
8
10
  return (origin, c) => origin === new URL(c.req.url).origin;
9
11
  } else if (typeof optsOrigin === "string") {
@@ -18,13 +20,31 @@ var csrf = (options) => {
18
20
  if (origin === void 0) {
19
21
  return false;
20
22
  }
21
- return handler(origin, c);
23
+ return originHandler(origin, c);
24
+ };
25
+ const secFetchSiteHandler = ((optsSecFetchSite) => {
26
+ if (!optsSecFetchSite) {
27
+ return (secFetchSite) => secFetchSite === "same-origin";
28
+ } else if (typeof optsSecFetchSite === "string") {
29
+ return (secFetchSite) => secFetchSite === optsSecFetchSite;
30
+ } else if (typeof optsSecFetchSite === "function") {
31
+ return optsSecFetchSite;
32
+ } else {
33
+ return (secFetchSite) => optsSecFetchSite.includes(secFetchSite);
34
+ }
35
+ })(options?.secFetchSite);
36
+ const isAllowedSecFetchSite = (secFetchSite, c) => {
37
+ if (secFetchSite === void 0) {
38
+ return false;
39
+ }
40
+ if (!isSecFetchSite(secFetchSite)) {
41
+ return false;
42
+ }
43
+ return secFetchSiteHandler(secFetchSite, c);
22
44
  };
23
45
  return async function csrf2(c, next) {
24
- if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedOrigin(c.req.header("origin"), c)) {
25
- const res = new Response("Forbidden", {
26
- status: 403
27
- });
46
+ if (!isSafeMethodRe.test(c.req.method) && isRequestedByFormElementRe.test(c.req.header("content-type") || "text/plain") && !isAllowedSecFetchSite(c.req.header("sec-fetch-site"), c) && !isAllowedOrigin(c.req.header("origin"), c)) {
47
+ const res = new Response("Forbidden", { status: 403 });
28
48
  throw new HTTPException(403, { res });
29
49
  }
30
50
  await next();
@@ -3,7 +3,9 @@ var mergeBuffers = (buffer1, buffer2) => {
3
3
  if (!buffer1) {
4
4
  return buffer2;
5
5
  }
6
- const merged = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
6
+ const merged = new Uint8Array(
7
+ new ArrayBuffer(buffer1.byteLength + buffer2.byteLength)
8
+ );
7
9
  merged.set(new Uint8Array(buffer1), 0);
8
10
  merged.set(buffer2, buffer1.byteLength);
9
11
  return merged;
@@ -82,6 +82,6 @@ interface CloudFrontResult {
82
82
  bodyEncoding?: "text" | "base64";
83
83
  }
84
84
  export declare const handle: (app: Hono<any>) => ((event: CloudFrontEdgeEvent, context?: CloudFrontContext, callback?: Callback) => Promise<CloudFrontResult>);
85
- export declare const createBody: (method: string, requestBody: CloudFrontRequest["body"]) => string | Uint8Array | undefined;
85
+ export declare const createBody: (method: string, requestBody: CloudFrontRequest["body"]) => string | Uint8Array<ArrayBuffer> | undefined;
86
86
  export declare const isContentTypeBinary: (contentType: string) => boolean;
87
87
  export {};
@@ -9,7 +9,7 @@ type HeaderRecord = Record<"Content-Type", BaseMime> | Record<ResponseHeader, st
9
9
  /**
10
10
  * Data type can be a string, ArrayBuffer, Uint8Array (buffer), or ReadableStream.
11
11
  */
12
- export type Data = string | ArrayBuffer | ReadableStream | Uint8Array;
12
+ export type Data = string | ArrayBuffer | ReadableStream | Uint8Array<ArrayBuffer>;
13
13
  /**
14
14
  * Interface for the execution context in a web worker or similar environment.
15
15
  */
@@ -6,7 +6,7 @@ import type { Context } from '../../context';
6
6
  export type Runtime = "node" | "deno" | "bun" | "workerd" | "fastly" | "edge-light" | "other";
7
7
  export declare const env: <T extends Record<string, unknown>, C extends Context = Context<{
8
8
  Bindings: T;
9
- }, any, {}>>(c: T extends Record<string, unknown> ? Context : C, runtime?: Runtime) => T & C["env"];
9
+ }>>(c: T extends Record<string, unknown> ? Context : C, runtime?: Runtime) => T & C["env"];
10
10
  export declare const knownUserAgents: Partial<Record<Runtime, string>>;
11
11
  export declare const getRuntimeKey: () => Runtime;
12
12
  export declare const checkUserAgentEquals: (platform: string) => boolean;
@@ -9,6 +9,7 @@ interface ProxyRequestInit extends Omit<RequestInit, "headers"> {
9
9
  string,
10
10
  string
11
11
  ][] | Record<RequestHeader, string | undefined> | Record<string, string | undefined>;
12
+ customFetch?: (request: Request) => Promise<Response>;
12
13
  }
13
14
  interface ProxyFetch {
14
15
  (input: string | URL | Request, init?: ProxyRequestInit): Promise<Response>;
@@ -49,7 +49,7 @@ export interface SendOptions {
49
49
  */
50
50
  export declare class WSContext<T = unknown> {
51
51
  constructor(init: WSContextInit<T>);
52
- send(source: string | ArrayBuffer | Uint8Array, options?: SendOptions): void;
52
+ send(source: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: SendOptions): void;
53
53
  raw?: T;
54
54
  binaryType: BinaryType;
55
55
  get readyState(): WSReadyState;
@@ -5,44 +5,78 @@
5
5
  import type { Context } from '../../context';
6
6
  import type { MiddlewareHandler } from '../../types';
7
7
  type IsAllowedOriginHandler = (origin: string, context: Context) => boolean;
8
+ declare const secFetchSiteValues: readonly [
9
+ "same-origin",
10
+ "same-site",
11
+ "none",
12
+ "cross-site"
13
+ ];
14
+ type SecFetchSite = (typeof secFetchSiteValues)[number];
15
+ type IsAllowedSecFetchSiteHandler = (secFetchSite: SecFetchSite, context: Context) => boolean;
8
16
  interface CSRFOptions {
9
17
  origin?: string | string[] | IsAllowedOriginHandler;
18
+ secFetchSite?: SecFetchSite | SecFetchSite[] | IsAllowedSecFetchSiteHandler;
10
19
  }
11
20
  /**
12
21
  * CSRF Protection Middleware for Hono.
13
22
  *
23
+ * Protects against Cross-Site Request Forgery attacks by validating request origins
24
+ * and sec-fetch-site headers. The request is allowed if either validation passes.
25
+ *
14
26
  * @see {@link https://hono.dev/docs/middleware/builtin/csrf}
15
27
  *
16
28
  * @param {CSRFOptions} [options] - The options for the CSRF protection middleware.
17
- * @param {string|string[]|(origin: string, context: Context) => boolean} [options.origin] - Specify origins.
29
+ * @param {string|string[]|(origin: string, context: Context) => boolean} [options.origin] -
30
+ * Allowed origins for requests.
31
+ * - string: Single allowed origin (e.g., 'https://example.com')
32
+ * - string[]: Multiple allowed origins
33
+ * - function: Custom validation logic
34
+ * - Default: Only same origin as the request URL
35
+ * @param {string|string[]|(secFetchSite: string, context: Context) => boolean} [options.secFetchSite] -
36
+ * Sec-Fetch-Site header validation. Standard values include 'same-origin', 'same-site', 'cross-site', 'none'.
37
+ * - string: Single allowed value (e.g., 'same-origin')
38
+ * - string[]: Multiple allowed values (e.g., ['same-origin', 'same-site'])
39
+ * - function: Custom validation with access to context
40
+ * - Default: Only allows 'same-origin'
18
41
  * @returns {MiddlewareHandler} The middleware handler function.
19
42
  *
20
43
  * @example
21
44
  * ```ts
22
45
  * const app = new Hono()
23
46
  *
24
- * app.use(csrf())
25
- *
26
- * // Specifying origins with using `origin` option
27
- * // string
28
- * app.use(csrf({ origin: 'myapp.example.com' }))
29
- *
30
- * // string[]
31
- * app.use(
32
- * csrf({
33
- * origin: ['myapp.example.com', 'development.myapp.example.com'],
34
- * })
35
- * )
36
- *
37
- * // Function
38
- * // It is strongly recommended that the protocol be verified to ensure a match to `$`.
39
- * // You should *never* do a forward match.
40
- * app.use(
41
- * '*',
42
- * csrf({
43
- * origin: (origin) => /https:\/\/(\w+\.)?myapp\.example\.com$/.test(origin),
44
- * })
45
- * )
47
+ * // Default: both origin and sec-fetch-site validation
48
+ * app.use('*', csrf())
49
+ *
50
+ * // Allow specific origins
51
+ * app.use('*', csrf({ origin: 'https://example.com' }))
52
+ * app.use('*', csrf({ origin: ['https://app.com', 'https://api.com'] }))
53
+ *
54
+ * // Allow specific sec-fetch-site values
55
+ * app.use('*', csrf({ secFetchSite: 'same-origin' }))
56
+ * app.use('*', csrf({ secFetchSite: ['same-origin', 'same-site'] }))
57
+ *
58
+ * // Dynamic sec-fetch-site validation
59
+ * app.use('*', csrf({
60
+ * secFetchSite: (secFetchSite, c) => {
61
+ * // Always allow same-origin
62
+ * if (secFetchSite === 'same-origin') return true
63
+ * // Allow cross-site for webhook endpoints
64
+ * if (secFetchSite === 'cross-site' && c.req.path.startsWith('/webhook/')) {
65
+ * return true
66
+ * }
67
+ * return false
68
+ * }
69
+ * }))
70
+ *
71
+ * // Dynamic origin validation
72
+ * app.use('*', csrf({
73
+ * origin: (origin, c) => {
74
+ * // Allow same origin
75
+ * if (origin === new URL(c.req.url).origin) return true
76
+ * // Allow specific trusted domains
77
+ * return ['https://app.example.com', 'https://admin.example.com'].includes(origin)
78
+ * }
79
+ * }))
46
80
  * ```
47
81
  */
48
82
  export declare const csrf: (options?: CSRFOptions) => MiddlewareHandler;
@@ -1 +1 @@
1
- export declare const generateDigest: (stream: ReadableStream<Uint8Array> | null, generator: (body: Uint8Array) => ArrayBuffer | Promise<ArrayBuffer>) => Promise<string | null>;
1
+ export declare const generateDigest: (stream: ReadableStream<Uint8Array<ArrayBuffer>> | null, generator: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>) => Promise<string | null>;
@@ -6,7 +6,7 @@ import type { MiddlewareHandler } from '../../types';
6
6
  type ETagOptions = {
7
7
  retainedHeaders?: string[];
8
8
  weak?: boolean;
9
- generateDigest?: (body: Uint8Array) => ArrayBuffer | Promise<ArrayBuffer>;
9
+ generateDigest?: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>;
10
10
  };
11
11
  /**
12
12
  * Default headers to pass through on 304 responses. From the spec:
@@ -1,3 +1,4 @@
1
+ import { GET_MATCH_RESULT } from './request/constants';
1
2
  import type { Result } from './router';
2
3
  import type { Input, InputToDataByTarget, ParamKeyToRecord, ParamKeys, RemoveQuestion, RouterRoute, ValidationTargets } from './types';
3
4
  import type { BodyData, ParseBodyOptions } from './utils/body';
@@ -14,6 +15,10 @@ type BodyCache = Partial<Body & {
14
15
  parsedBody: BodyData;
15
16
  }>;
16
17
  export declare class HonoRequest<P extends string = "/", I extends Input["out"] = {}> {
18
+ [GET_MATCH_RESULT]: Result<[
19
+ unknown,
20
+ RouterRoute
21
+ ]>;
17
22
  /**
18
23
  * `.raw` can get the raw Request object.
19
24
  *
@@ -2,7 +2,7 @@
2
2
  * @module
3
3
  * Encode utility.
4
4
  */
5
- export declare const decodeBase64Url: (str: string) => Uint8Array;
5
+ export declare const decodeBase64Url: (str: string) => Uint8Array<ArrayBuffer>;
6
6
  export declare const encodeBase64Url: (buf: ArrayBufferLike) => string;
7
7
  export declare const encodeBase64: (buf: ArrayBufferLike) => string;
8
- export declare const decodeBase64: (str: string) => Uint8Array;
8
+ export declare const decodeBase64: (str: string) => Uint8Array<ArrayBuffer>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "4.9.2",
3
+ "version": "4.9.3",
4
4
  "description": "Web framework built on Web Standards",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -10,16 +10,16 @@
10
10
  "dist"
11
11
  ],
12
12
  "scripts": {
13
- "test": "tsc --noEmit && vitest --run && vitest -c .vitest.config/jsx-runtime-default.ts --run && vitest -c .vitest.config/jsx-runtime-dom.ts --run",
13
+ "test": "tsc --noEmit && vitest --run",
14
14
  "test:watch": "vitest --watch",
15
15
  "test:deno": "deno test --allow-read --allow-env --allow-write --allow-net -c runtime-tests/deno/deno.json runtime-tests/deno && deno test --no-lock -c runtime-tests/deno-jsx/deno.precompile.json runtime-tests/deno-jsx && deno test --no-lock -c runtime-tests/deno-jsx/deno.react-jsx.json runtime-tests/deno-jsx",
16
16
  "test:bun": "bun test --jsx-import-source ../../src/jsx runtime-tests/bun/*",
17
- "test:fastly": "vitest --run --config ./runtime-tests/fastly/vitest.config.ts",
18
- "test:node": "vitest --run --config ./runtime-tests/node/vitest.config.ts",
19
- "test:workerd": "vitest --run --config ./runtime-tests/workerd/vitest.config.ts",
20
- "test:lambda": "vitest --run --config ./runtime-tests/lambda/vitest.config.ts",
21
- "test:lambda-edge": "vitest --run --config ./runtime-tests/lambda-edge/vitest.config.ts",
22
- "test:all": "bun run test && bun test:deno && bun test:bun && bun test:fastly && bun test:node && bun test:workerd && bun test:lambda && bun test:lambda-edge",
17
+ "test:fastly": "vitest --run --project fastly",
18
+ "test:node": "vitest --run --project node",
19
+ "test:workerd": "vitest --run --project workerd",
20
+ "test:lambda": "vitest --run --project lambda",
21
+ "test:lambda-edge": "vitest --run --project lambda-edge",
22
+ "test:all": "bun run test && bun test:deno && bun test:bun",
23
23
  "lint": "eslint src runtime-tests build perf-measures benchmarks",
24
24
  "lint:fix": "eslint src runtime-tests build perf-measures benchmarks --fix",
25
25
  "format": "prettier --check --cache \"src/**/*.{js,ts,tsx}\" \"runtime-tests/**/*.{js,ts,tsx}\" \"build/**/*.{js,ts,tsx}\" \"perf-measures/**/*.{js,ts,tsx}\" \"benchmarks/**/*.{js,ts,tsx}\"",
@@ -658,14 +658,14 @@
658
658
  "devDependencies": {
659
659
  "@hono/eslint-config": "^2.0.3",
660
660
  "@hono/node-server": "^1.13.5",
661
- "@types/glob": "^8.1.0",
661
+ "@types/glob": "^9.0.0",
662
662
  "@types/jsdom": "^21.1.7",
663
- "@types/node": "^22.0.0",
664
- "@types/supertest": "^2.0.16",
663
+ "@types/node": "^24.3.0",
664
+ "@types/supertest": "^6.0.3",
665
665
  "@typescript/native-preview": "7.0.0-dev.20250523.1",
666
- "@vitest/coverage-v8": "^3.0.5",
666
+ "@vitest/coverage-v8": "^3.2.4",
667
667
  "arg": "^5.0.2",
668
- "bun-types": "^1.1.39",
668
+ "bun-types": "^1.2.20",
669
669
  "editorconfig-checker": "^6.1.0",
670
670
  "esbuild": "^0.15.18",
671
671
  "eslint": "^9.10.0",
@@ -677,13 +677,14 @@
677
677
  "prettier": "^2.6.2",
678
678
  "publint": "^0.1.16",
679
679
  "supertest": "^6.3.4",
680
- "typescript": "^5.3.3",
680
+ "typescript": "^5.9.2",
681
681
  "vite-plugin-fastly-js-compute": "^0.4.2",
682
- "vitest": "^3.0.5",
682
+ "vitest": "^3.2.4",
683
683
  "wrangler": "4.12.0",
684
684
  "ws": "^8.18.0",
685
685
  "zod": "^3.23.8"
686
686
  },
687
+ "packageManager": "bun@1.2.20",
687
688
  "engines": {
688
689
  "node": ">=16.9.0"
689
690
  }