Relevant source files
The following files were used as context for generating this wiki page:
- src/middleware/body-limit/index.ts
- src/middleware/secure-headers/secure-headers.ts
- src/middleware/csrf/index.ts
- src/middleware/ip-restriction/index.ts
- src/adapter/aws-lambda/handler.ts
- src/middleware/method-override/index.ts
- src/request.ts
- src/utils/body.ts
- src/middleware/jwk/jwk.ts
- src/middleware/jwt/jwt.ts
- src/validator/validator.ts
- src/middleware/cors/index.ts
- src/middleware/etag/index.ts
- src/helper/streaming/text.ts
- src/middleware/bearer-auth/index.ts
- src/adapter/lambda-edge/handler.ts
- src/middleware/compress/index.ts
- src/middleware/language/language.ts
- src/utils/buffer.ts
- src/middleware/etag/digest.ts
- src/client/fetch-result-please.ts
- src/context.ts
- src/adapter/vercel/conninfo.ts
- src/adapter/lambda-edge/conninfo.ts
- src/helper/streaming/utils.ts
- src/middleware/secure-headers/index.ts
- src/utils/compress.ts
- src/middleware/request-id/request-id.ts
- src/middleware/secure-headers/permissions-policy.ts
- src/middleware/jwk/keys.test.json
Overview
Hono provides built-in middleware to protect web applications against Cross-Site Request Forgery (CSRF) and control inbound request payload sizesCSRF Protection Middleware for Hono, Body Limit Middleware for Hono. The CSRF middleware evaluates request security by validating Origin headers and Sec-Fetch-Site values against safe and mutating HTTP method checksCSRF Protection Middleware for Hono. Concurrently, the body limit middleware safeguards server resources by inspecting Content-Length headers or incrementally tracking streaming request bodies against configured maximum size limitsBody Limit Middleware for Hono, src/middleware/body-limit/index.ts:61-94. Together, these components enforce robust request boundaries across diverse edge and serverless environments. Sources: CSRF Protection Middleware for Hono, Body Limit Middleware for Hono, src/middleware/csrf/index.ts:32-36, src/middleware/body-limit/index.ts:18-25, src/middleware/body-limit/index.ts:61-94
Cross-Site Request Forgery Protection Architecture
Public API Surface and Method Checks
Hono exports the csrf() factory function which returns a standard MiddlewareHandlersrc/middleware/csrf/index.ts:94-150. The middleware evaluates incoming requests against safe and mutating HTTP method classificationssrc/middleware/csrf/index.ts:139-144. Safe methods are matched using the regular expression /^(GET|HEAD)$/, exempting read-only requests from validationsrc/middleware/csrf/index.ts:28|28, src/middleware/csrf/index.ts:139-140. Mutating requests undergo inspection only if their content-type header matches form element submission patterns defined by /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/isrc/middleware/csrf/index.ts:29-30, src/middleware/csrf/index.ts:140-141. If both sec-fetch-site and origin validations fail on a matching mutating request, the middleware instantiates a Response with body 'Forbidden' and status 403, throwing an HTTPException(403)src/middleware/csrf/index.ts:142-147. Otherwise, control advances via await next()src/middleware/csrf/index.ts:149-149.
const isSafeMethodRe = /^(GET|HEAD)$/
const isRequestedByFormElementRe =
/^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/iSources: src/middleware/csrf/index.ts:28-30, src/middleware/csrf/index.ts:94-150
return async function csrf(c, next) {
if (
!isSafeMethodRe.test(c.req.method) &&
isRequestedByFormElementRe.test(c.req.header('content-type') || 'text/plain') &&
!(await isAllowedSecFetchSite(c.req.header('sec-fetch-site'), c)) &&
!(await isAllowedOrigin(c.req.header('origin'), c))
) {
const res = new Response('Forbidden', { status: 403 })
throw new HTTPException(403, { res })
}
await next()
}Sources: src/middleware/csrf/index.ts:138-150
Warning
Requests lacking both sec-fetch-site and origin headers during mutating form submissions will fail validation because both isAllowedSecFetchSite and isAllowedOrigin return false when their respective headers are undefined.
Sources: src/middleware/csrf/index.ts:106-112, src/middleware/csrf/index.ts:126-136
Verification Call Chain
The execution trace for Sec-Fetch-Site validation flows through distinct functions defined across the module.
csrf— Intercepts the request inside the returned middleware handler and invokesisAllowedSecFetchSitewith thesec-fetch-siteheadersrc/middleware/csrf/index.ts:138-142.isAllowedSecFetchSite— Checks whether the header value is defined and passes the type guardisSecFetchSitebefore calling the configuredsecFetchSiteHandlersrc/middleware/csrf/index.ts:126-136.isSecFetchSite— Validates that the string matches one of the permitted union constantssrc/middleware/csrf/index.ts:15-16.
Sources: src/middleware/csrf/index.ts:15-16, src/middleware/csrf/index.ts:126-136, src/middleware/csrf/index.ts:138-142
Permitted Sec-Fetch-Site Constants
The module defines explicit runtime values and type constraints for browser-supplied Sec-Fetch-Site headers.
| Constant Array / Type | Value | Meaning / Role |
|---|---|---|
secFetchSiteValues | 'same-origin' | Request originated from the same site and origin. |
secFetchSiteValues | 'same-site' | Request originated from a different path on the same site. |
secFetchSiteValues | 'none' | Request was triggered by a direct user interaction like bookmark navigation. |
secFetchSiteValues | 'cross-site' | Request originated from a completely external domain. |
const secFetchSiteValues = ['same-origin', 'same-site', 'none', 'cross-site'] as const
type SecFetchSite = (typeof secFetchSiteValues)[number]
const isSecFetchSite = (value: string): value is SecFetchSite =>
(secFetchSiteValues as readonly string[]).includes(value)Sources: src/middleware/csrf/index.ts:12-16
CSRF Origin Resolution and Custom Matchers
Origin and Sec-Fetch-Site Matcher Resolution
The CSRF middleware normalizes both origin and secFetchSite options during initialization through higher-order function factories. When options.origin is omitted, the default handler extracts the origin from the request URL using new URL(c.req.url).origin. If options.origin is provided as a string, it constructs an exact equality matcher. If it is an array, it evaluates membership using optsOrigin.includes(origin). When supplied as a custom function, the middleware delegates directly to the user-defined handler callback.
const originHandler: IsAllowedOriginHandler = ((optsOrigin) => {
if (!optsOrigin) {
return (origin, c) => origin === new URL(c.req.url).origin
} else if (typeof optsOrigin === 'string') {
return (origin) => origin === optsOrigin
} else if (typeof optsOrigin === 'function') {
return optsOrigin
} else {
return (origin) => optsOrigin.includes(origin)
}
})(options?.origin)Sources: src/middleware/csrf/index.ts:95-105
Similarly, secFetchSiteHandler builds its validation logic based on the input type of options.secFetchSite. If undefined, it restricts permitted traffic exclusively to 'same-origin'. String, array, and function options follow identical resolution branches to evaluate browser metadata against configured rules.
const secFetchSiteHandler: IsAllowedSecFetchSiteHandler = ((optsSecFetchSite) => {
if (!optsSecFetchSite) {
// Default: only allow same-origin
return (secFetchSite) => secFetchSite === 'same-origin'
} else if (typeof optsSecFetchSite === 'string') {
return (secFetchSite) => secFetchSite === optsSecFetchSite
} else if (typeof optsSecFetchSite === 'function') {
return optsSecFetchSite
} else {
return (secFetchSite) => optsSecFetchSite.includes(secFetchSite)
}
})(options?.secFetchSite)Sources: src/middleware/csrf/index.ts:114-125
Note
If the origin header is entirely absent from the incoming request, isAllowedOrigin immediately short-circuits and returns false without invoking the underlying handler.
Sources: src/middleware/csrf/index.ts:106-112
Custom Matcher Signatures and Handlers
Both origin and secFetchSite support asynchronous or synchronous custom handler functions that receive the header value alongside the active execution Context. These hooks allow developers to implement granular policies such as validating specific subdomain patterns or permitting cross-site requests exclusively for designated webhook endpoints.
export type IsAllowedOriginHandler = (origin: string, context: Context) => boolean | Promise<boolean>
export type IsAllowedSecFetchSiteHandler = (
secFetchSite: SecFetchSite,
context: Context
) => boolean | Promise<boolean>type IsAllowedOriginHandler = (origin: string, context: Context) => boolean | Promise<boolean>
const secFetchSiteValues = ['same-origin', 'same-site', 'none', 'cross-site'] as const
type SecFetchSite = (typeof secFetchSiteValues)[number]
const isSecFetchSite = (value: string): value is SecFetchSite =>
(secFetchSiteValues as readonly string[]).includes(value)
type IsAllowedSecFetchSiteHandler = (
secFetchSite: SecFetchSite,
context: Context
) => boolean | Promise<boolean>Sources: src/middleware/csrf/index.ts:10-21
Warning
When sec-fetch-site contains an unrecognised string value not present in the runtime secFetchSiteValues tuple, isSecFetchSite evaluates to false and causes validation to fail before the custom secFetchSiteHandler can execute.
Sources: src/middleware/csrf/index.ts:12-16, src/middleware/csrf/index.ts:131-135
Body Limit Middleware and Stream Wrapping
Public API Surface and Configuration Options
The body limit middleware restricts incoming request payload sizes to protect applications against denial-of-service attacks Execution Context caused by oversized payloads. It exports a bodyLimit factory function that accepts a BodyLimitOptions configuration object specifying the maxSize threshold in bytes and an optional custom error handler function onError.
| Option Property | Type | Default Behavior | Purpose |
|---|---|---|---|
maxSize | number | Required | Maximum allowable body size in bytes. |
onError | OnError | Throws HTTPException(413) with text 'Payload Too Large' | Handler invoked when maxSize is exceeded. |
type OnError = (c: Context) => Response | Promise<Response>
type BodyLimitOptions = {
maxSize: number
onError?: OnError
}Sources: src/middleware/body-limit/index.ts:12-16
export const bodyLimit = (options: BodyLimitOptions): MiddlewareHandler => {
const onError: OnError =
options.onError ||
(() => {
const res = new Response(ERROR_MESSAGE, {
status: 413,
})
throw new HTTPException(413, { res })
})
const maxSize = options.maxSizeSources: src/middleware/body-limit/index.ts:50-59
Note
If options.onError is omitted, the default error handler constructs a standard Web Response with status code 413 and throws an HTTPException.
Sources: src/middleware/body-limit/index.ts:51-58
Content-Length Pre-Checks and Chunk Tracking
When an incoming request reaches the middleware, it first evaluates whether a request body exists. If c.req.raw.body is null or undefined (such as standard GET or HEAD requests), the middleware bypasses inspection and invokes next() immediately.
return async function bodyLimit(c, next) {
if (!c.req.raw.body) {
// maybe GET or HEAD request
return next()
}Sources: src/middleware/body-limit/index.ts:61-65
If headers contain a content-length without a transfer-encoding header, the middleware performs a fast pre-check by parsing the length directly. If content-length exceeds maxSize, it immediately invokes onError(c) without consuming the stream.
const hasTransferEncoding = c.req.raw.headers.has('transfer-encoding')
const hasContentLength = c.req.raw.headers.has('content-length')
if (hasContentLength && !hasTransferEncoding) {
// Only Content-Length present - we can trust it
const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10)
return contentLength > maxSize ? onError(c) : next()
}Sources: src/middleware/body-limit/index.ts:67-74
When transfer-encoding (chunked) is present or length headers are missing entirely, per RFC 7230, transfer-encoding takes precedence. The middleware consumes the underlying ReadableStream chunk by chunk using a ReadableStreamDefaultReader, accumulating byte lengths into a running size counter and pushing each Uint8Array chunk into an array. If accumulated size exceeds maxSize at any point during iteration, processing terminates instantly and invokes onError(c).
// Transfer-Encoding present (chunked) or no length headers.
// Per RFC 7230, when both are present Transfer-Encoding takes precedence
// and Content-Length is ignored. Read the body up-front so the size check
// is final before the handler runs, regardless of how (or whether) the
// handler reads the body.
let size = 0
const chunks: Uint8Array[] = []
const rawReader = c.req.raw.body.getReader()
for (;;) {
const { done, value } = await rawReader.read()
if (done) {
break
}
size += value.length
if (size > maxSize) {
return onError(c)
}
chunks.push(value)
}Sources: src/middleware/body-limit/index.ts:76-94
Warning
Because chunked or length-less streams are read entirely into memory arrays during validation, extremely large streaming payloads will consume memory proportional to their size up to the threshold before being rejected or forwarded. Sources: src/middleware/body-limit/index.ts:81-94
Streaming Body Reconstruction and Replacement
Once a chunked or un-sized stream successfully passes the byte limit verification, the accumulated chunks must be restored so downstream route handlers and parsers can read the request body normally. The middleware constructs a fresh ReadableStream that enqueues the saved Uint8Array chunks in sequence and closes the controller.
const requestInit: RequestInit & { duplex: 'half' } = {
body: new ReadableStream({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(chunk)
}
controller.close()
},
}),Sources: src/middleware/body-limit/index.ts:96-104
It then instantiates a new standard Request object wrapping the fresh readable stream with explicit duplex: 'half' options, assigns it back to c.req.raw, and proceeds down the middleware chain via next().
duplex: 'half',
}
c.req.raw = new Request(c.req.raw, requestInit as RequestInit)
Sources: src/middleware/body-limit/index.ts:105-108
Payload Overflow Detection and Error Handling
Error Trigger Conditions and HTTPException 413
When the body limit middleware detects that a payload exceeds the configured maxSize, it immediately halts further stream consumption or execution and invokes the error handler. For requests governed by a trustworthy content-length header, this triggers as soon as content-length > maxSize. For chunked or un-sized streams, the check occurs incrementally during stream iteration inside the reader loop whenever the running size counter surpasses maxSize.
if (hasContentLength && !hasTransferEncoding) {
// Only Content-Length present - we can trust it
const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10)
return contentLength > maxSize ? onError(c) : next()
}Sources: src/middleware/body-limit/index.ts:70-74
size += value.length
if (size > maxSize) {
return onError(c)
}
chunks.push(value)
}Sources: src/middleware/body-limit/index.ts:89-94
Caution
When consuming chunked streams, the reader halts the moment size > maxSize is met mid-stream, preventing excess bytes from accumulating into memory while terminating the active request stream early.
Sources: src/middleware/body-limit/index.ts:89-94
Custom onError Callbacks and Default Response Termination
Developers can supply a custom onError callback function within BodyLimitOptions to customize how payload overflows are formatted and returned to clients. If options.onError is omitted, the middleware falls back to a default handler that instantiates a web standard Response using the constant ERROR_MESSAGE string ('Payload Too Large') with status code 413, and immediately throws an HTTPException(413, { res }).
const ERROR_MESSAGE = 'Payload Too Large'
type OnError = (c: Context) => Response | Promise<Response>
type BodyLimitOptions = {
maxSize: number
onError?: OnError
}Sources: src/middleware/body-limit/index.ts:10-16
export const bodyLimit = (options: BodyLimitOptions): MiddlewareHandler => {
const onError: OnError =
options.onError ||
(() => {
const res = new Response(ERROR_MESSAGE, {
status: 413,
})
throw new HTTPException(413, { res })
})Sources: src/middleware/body-limit/index.ts:50-58
return contentLength > maxSize ? onError(c) : next()
}Sources: src/middleware/body-limit/index.ts:73-74
if (size > maxSize) {
return onError(c)
}Sources: src/middleware/body-limit/index.ts:90-92
Note
The OnError type signature specifies that custom handlers accept the active Context (c) and return either a Response or a Promise<Response>, allowing seamless integration with context response helpers like c.text().
Sources: src/middleware/body-limit/index.ts:12-16
Security Configuration and Defense in Depth
Proxy Adapters and Edge Request Initialization
When deploying security middleware like CSRF protection and body limits behind serverless runtimes or edge proxies, incoming event structures must be accurately transformed into web standard Request instances. Adapters such as aws-lambda and lambda-edge process incoming events by parsing HTTP headers, methods, query strings, and body payloads before dispatching them into Hono's app.fetch().
const createRequest = (event: CloudFrontEdgeEvent): Request => {
const queryString = event.Records[0].cf.request.querystring
const host =
event.Records[0].cf.request.headers?.host?.[0]?.value ||
event.Records[0].cf.config.distributionDomainName
const urlPath = `https://${host}${event.Records[0].cf.request.uri}`
const url = queryString ? `${urlPath}?${queryString}` : urlPath
const headers = new Headers()
Object.entries(event.Records[0].cf.request.headers).forEach(([k, v]) => {
v.forEach((header) => headers.append(k, header.value))
})
const requestBody = event.Records[0].cf.request.body
const method = event.Records[0].cf.request.method
const rawBody = createBody(method, requestBody)
let body: string | Uint8Array<ArrayBuffer> | undefined = rawBody
if (rawBody !== undefined) {
const bytes =
typeof rawBody === 'string'
? (new TextEncoder().encode(rawBody) as Uint8Array<ArrayBuffer>)
: rawBody
body = bytes
headers.set('content-length', bytes.length.toString())
}
return new Request(url, {
headers,
method,
body,
})
}Sources: src/adapter/lambda-edge/handler.ts:152-184
Important
Edge adapters like Lambda@Edge manually assign content-length headers when request body bytes are present, ensuring downstream size validation and body limit middleware can inspect exact payload dimensions before handlers execute. Sources: src/adapter/lambda-edge/handler.ts:169-177
Combining CSRF Protection and Body Constraints
Layering both csrf() and bodyLimit() creates a multi-layered defense against malicious web requests. The CSRF middleware evaluates mutating HTTP methods against form element content-types (application/x-www-form-urlencoded, multipart/form-data, text/plain), requiring valid sec-fetch-site or origin validation headers before allowing execution to proceed.
return async function csrf(c, next) {
if (
!isSafeMethodRe.test(c.req.method) &&
isRequestedByFormElementRe.test(c.req.header('content-type') || 'text/plain') &&
!(await isAllowedSecFetchSite(c.req.header('sec-fetch-site'), c)) &&
!(await isAllowedOrigin(c.req.header('origin'), c))
) {
const res = new Response('Forbidden', { status: 403 })
throw new HTTPException(403, { res })
}
await next()
}Sources: src/middleware/csrf/index.ts:138-150
Once origin and site context validation pass, the body limit middleware intercepts incoming streams, enforcing strict maxSize thresholds to reject oversized payloads with an HTTPException(413) before memory exhaustion occurs.
return async function bodyLimit(c, next) {
if (!c.req.raw.body) {
// maybe GET or HEAD request
return next()
}
const hasTransferEncoding = c.req.raw.headers.has('transfer-encoding')
const hasContentLength = c.req.raw.headers.has('content-length')
if (hasContentLength && !hasTransferEncoding) {
// Only Content-Length present - we can trust it
const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10)
return contentLength > maxSize ? onError(c) : next()
}Sources: src/middleware/body-limit/index.ts:61-74
Related
On This Page
OverviewCross-Site Request Forgery Protection ArchitecturePublic API Surface and Method ChecksVerification Call ChainPermitted Sec-Fetch-Site ConstantsCSRF Origin Resolution and Custom MatchersOrigin and Sec-Fetch-Site Matcher ResolutionCustom Matcher Signatures and HandlersBody Limit Middleware and Stream WrappingPublic API Surface and Configuration OptionsContent-Length Pre-Checks and Chunk TrackingStreaming Body Reconstruction and ReplacementPayload Overflow Detection and Error HandlingError Trigger Conditions and HTTPException 413Custom onError Callbacks and Default Response TerminationSecurity Configuration and Defense in DepthProxy Adapters and Edge Request InitializationCombining CSRF Protection and Body ConstraintsRelated