---
title: "IP Restriction"
description: "The IP restriction middleware restricts incoming HTTP requests by evaluating client IP addresses against configurable allow and deny rules. It processes static IP addresses, CIDR notation subnets f..."
last_updated: "2026-10-12T08:14:12.743647+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/security-middleware/ip-restriction"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/middleware/ip-restriction/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts)
- [src/utils/ipaddr.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts)
- [src/middleware/secure-headers/secure-headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/secure-headers.ts)
- [src/adapter/aws-lambda/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/handler.ts)
- [src/adapter/vercel/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/vercel/conninfo.ts)
- [src/router/reg-exp-router/prepared-router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/prepared-router.ts)
- [src/request.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/request.ts)
- [src/adapter/lambda-edge/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/conninfo.ts)
- [src/adapter/bun/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts)
- [src/validator/validator.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/validator/validator.ts)
- [src/router/reg-exp-router/router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/router.ts)
- [src/adapter/aws-lambda/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/conninfo.ts)
- [src/middleware/language/language.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/language/language.ts)
- [src/adapter/cloudflare-workers/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-workers/conninfo.ts)
- [src/utils/url.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/url.ts)
- [src/adapter/netlify/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/netlify/conninfo.ts)
- [src/router/linear-router/router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/linear-router/router.ts)
- [src/middleware/jwk/keys.test.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/keys.test.json)
- [src/adapter/cloudflare-pages/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/conninfo.ts)
- [src/helper/conninfo/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/conninfo/types.ts)
- [src/utils/accept.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/accept.ts)
- [src/router/pattern-router/router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/pattern-router/router.ts)
- [src/utils/compress.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/compress.ts)
- [src/adapter/deno/conninfo.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/conninfo.ts)
- [src/utils/cookie.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/cookie.ts)
- [src/helper/conninfo/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/conninfo/index.ts)
- [src/router/reg-exp-router/node.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/node.ts)
- [src/validator/utils.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/validator/utils.ts)
- [src/utils/headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/headers.ts)
- [src/middleware/secure-headers/permissions-policy.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/permissions-policy.ts)
</details>

## Overview

The IP restriction middleware restricts incoming HTTP requests by evaluating client IP addresses against configurable allow and deny rules. It processes static IP addresses, CIDR notation subnets for both IPv4 and IPv6, wildcard patterns, and custom validation functions.

Sources: [src/middleware/ip-restriction/index.ts:26-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L26-L38), [src/middleware/ip-restriction/index.ts:83-87](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L83-L87), [src/middleware/ip-restriction/index.ts:172-175](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L172-L175)

Client addresses are retrieved via connection info helpers across multiple [runtime adapters](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/runtime-adapters/runtime-adapters) and normalized for comparison. The middleware converts dotted-decimal IPv4 and hexadecimal IPv6 addresses into bigint binary representations, handling IPv4-mapped IPv6 formats and subnet bitmasks for precise access control decisions.

Sources: [src/middleware/ip-restriction/index.ts:10-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L10-L18), [src/middleware/ip-restriction/index.ts:104-114](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L104-L114), [src/middleware/ip-restriction/index.ts:131-139](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L131-L139)

## Middleware Configuration and Public Interface

### Public API Options and Types

The IP restriction middleware accepts a connection retrieval function and an options object containing configuration lists. The public interface defines rules that support wildcard matching, static addresses, CIDR notation subnets, and predicate functions [src/middleware/ip-restriction/index.ts:23-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L23-L38), [src/middleware/ip-restriction/index.ts:172-183](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L172-L183).

| Option / Type | Type Definition | Description |
| :--- | :--- | :--- |
| `GetIPAddr` | `GetConnInfo \| ((c: Context) => string)` | Function to retrieve the client IP address from context or connection info. |
| `IPRestrictionRule` | `string \| ((addr: { addr: string; type: AddressType }) => boolean)` | Rule definition accepting a literal pattern, CIDR range, wildcard, or evaluation callback. |
| `IPRestrictionRules` | `{ denyList?: IPRestrictionRule[]; allowList?: IPRestrictionRule[] }` | Container object holding optional deny and allow rule lists. |

Sources: [src/middleware/ip-restriction/index.ts:23-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L23-L38), [src/middleware/ip-restriction/index.ts:172-183](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L172-L183)

### Rule Formats and Matcher Compilation

Rule processing utilizes `buildMatcher` to compile rule arrays into optimized lookup sets and bitmasks [src/middleware/ip-restriction/index.ts:51-120](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L51-L120). The middleware evaluates incoming client requests against these compiled structures.

> [!WARNING]
> CIDR prefix validation strictly requires 1 to 3 decimal digits matching `/^[0-9]{1,3}$/` and throws a `TypeError` if the parsed integer exceeds 32 for IPv4 or 128 for IPv6 [src/middleware/ip-restriction/index.ts:41-50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L41-L50), [src/middleware/ip-restriction/index.ts:98](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L98).

Sources: [src/middleware/ip-restriction/index.ts:41-50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L41-L50), [src/middleware/ip-restriction/index.ts:51-120](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L51-L120)

## Client IP Extraction Across Runtimes

### Connection Information Interface and Adapters

Hono extracts remote client addresses through runtime-specific connection info helpers adhering to the `GetConnInfo` type signature. Each adapter accesses underlying request context properties or platform-specific headers to populate the `ConnInfo` structure containing remote address, address type, port, and transport protocol [src/helper/conninfo/types.ts:35-45](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/conninfo/types.ts#L35-L45).

| Runtime / Adapter | Source Mechanism | Extracted Properties |
| :--- | :--- | :--- |
| **Bun** | `server.requestIP(c.req.raw)` | `address`, `addressType` (`IPv4` / `IPv6`), `port` [src/adapter/bun/conninfo.ts:11-41](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts#L11-L41) |
| **Deno** | `c.env.remoteAddr` | `address` (`hostname`), `port`, `transport` [src/adapter/deno/conninfo.ts:9-15](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/conninfo.ts#L9-L15) |
| **Cloudflare Workers** | `c.req.header('cf-connecting-ip')` | `address` via header [src/adapter/cloudflare-workers/conninfo.ts:3-6](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-workers/conninfo.ts#L3-L6) |
| **Cloudflare Pages** | `c.req.header('cf-connecting-ip')` | `address` via header [src/adapter/cloudflare-pages/conninfo.ts:22-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/conninfo.ts#L22-L25) |
| **Vercel** | `c.req.header('x-real-ip')` | `address` via header [src/adapter/vercel/conninfo.ts:3-7](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/vercel/conninfo.ts#L3-L7) |
| **Netlify** | `c.env.context?.ip` | `address` from Netlify function context [src/adapter/netlify/conninfo.ts:53-56](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/netlify/conninfo.ts#L53-L56) |
| **AWS Lambda** | API Gateway v1/v2 or ALB `x-forwarded-for` | `address` from `requestContext.identity.sourceIp`, `requestContext.http.sourceIp`, or trailing XFF entry [src/adapter/aws-lambda/conninfo.ts:45-66](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/conninfo.ts#L45-L66) |
| **Lambda@Edge** | `c.env.event.Records[0].cf.request.clientIp` | `address` from CloudFront event record [src/adapter/lambda-edge/conninfo.ts:11-14](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/conninfo.ts#L11-L14) |

Sources: [src/adapter/bun/conninfo.ts:11-41](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts#L11-L41), [src/adapter/deno/conninfo.ts:9-15](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/conninfo.ts#L9-L15), [src/adapter/cloudflare-workers/conninfo.ts:3-6](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-workers/conninfo.ts#L3-L6), [src/adapter/cloudflare-pages/conninfo.ts:22-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/conninfo.ts#L22-L25), [src/adapter/vercel/conninfo.ts:3-7](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/vercel/conninfo.ts#L3-L7), [src/adapter/netlify/conninfo.ts:53-56](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/netlify/conninfo.ts#L53-L56), [src/adapter/aws-lambda/conninfo.ts:45-66](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/conninfo.ts#L45-L66), [src/adapter/lambda-edge/conninfo.ts:11-14](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/conninfo.ts#L11-L14)

### Specialized Adapter Extraction Behaviors

AWS Lambda handles multiple event payload structures by checking `requestContext.identity` for API Gateway REST APIs, `requestContext.http` for HTTP APIs and Function URLs, and falling back to parsing the comma-separated `x-forwarded-for` header where Application Load Balancers append the client IP to the end [src/adapter/aws-lambda/conninfo.ts:50-65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/conninfo.ts#L50-L65). Bun validates server existence and inspects `server.requestIP`, returning an empty remote object if the request is closed or uses a Unix domain socket [src/adapter/bun/conninfo.ts:19-34](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts#L19-L34).

> [!CAUTION]
> In Bun, if the server instance is missing from the environment or `server.requestIP` is not a function, a `TypeError` is thrown immediately during connection info resolution [src/adapter/bun/conninfo.ts:19-24](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts#L19-L24).

Sources: [src/adapter/aws-lambda/conninfo.ts:50-65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/conninfo.ts#L50-L65), [src/adapter/bun/conninfo.ts:19-34](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/conninfo.ts#L19-L34)

## IPv4 and Subnet Binary Conversion

### Dotted-Decimal Parsing and Bitwise Construction

Dotted-decimal IPv4 strings are processed character by character using `parseIPv4ToBinary`, converting the string representation into a 32-bit unsigned integer represented as a `bigint`. The parser iterates through the specified string range, verifying character ASCII codes against digits (`0`-`9`), dots (`.`), and octet value bounds. 

```typescript
const parseIPv4ToBinary = (
  ipv4: string,
  start: number,
  end: number,
  onInvalid: () => never
): bigint => {
  let result = 0n
  let octets = 0
  let octet = 0
  let digits = 0
  let firstDigit = 0

  for (let i = start; i <= end; i++) {
    const code = i < end ? ipv4.charCodeAt(i) : CHAR_CODE_DOT
    if (code >= CHAR_CODE_0 && code <= CHAR_CODE_9) {
      if (digits === 0) {
        firstDigit = code
      } else if (firstDigit === CHAR_CODE_0) {
        onInvalid()
      }
      octet = octet * 10 + code - CHAR_CODE_0
      if (octet > 255) {
        onInvalid()
      }
      digits++
      continue
    }

    if (code !== CHAR_CODE_DOT || digits === 0 || octets === 4) {
      onInvalid()
    }

    result = (result << 8n) + BigInt(octet)
    octets++
    octet = 0
    digits = 0
  }

  if (octets !== 4) {
    onInvalid()
  }

  return result
}
```
Sources: [src/utils/ipaddr.ts:83-126](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L83-L126)

Leading zeros within an octet (other than a standalone `0`) trigger validation failures to prevent ambiguity. Each parsed octet is shifted left by 8 bits (`result = (result << 8n) + BigInt(octet)`) as the parser progresses across dots.

> [!WARNING]
> An octet value exceeding 255, failing to provide exactly four dot-separated octets, or containing leading zeros like `192.168.01.1` will immediately invoke the error handler and throw an invalid IP exception.

Sources: [src/utils/ipaddr.ts:98-123](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L98-L123)

### CIDR Subnet Prefix Parsing and Bitmasks

CIDR notation rules are parsed by matching expressions against `IS_CIDR_NOTATION_REGEX = /\/[^/]*$/`. The prefix length is extracted and validated against numeric constraints via `parseCidrPrefix`, ensuring the prefix integer does not exceed 32 for IPv4 rules.

```typescript
const IS_CIDR_NOTATION_REGEX = /\/[^/]*$/
const parseCidrPrefix = (rule: string, prefix: string, max: number): number => {
  if (!/^[0-9]{1,3}$/.test(prefix)) {
    throw new TypeError(`Invalid rule: ${rule}`)
  }
  const parsedPrefix = parseInt(prefix)
  if (parsedPrefix > max) {
    throw new TypeError(`Invalid rule: ${rule}`)
  }
  return parsedPrefix
}
```
Sources: [src/middleware/ip-restriction/index.ts:40-50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L40-L50)

Once the prefix is verified, a bitmask is generated using bitwise shift operators. For an IPv4 CIDR range, the mask is constructed by shifting `1n` left by the prefix length, subtracting `1n`, and left-shifting the result by `32 - prefix`.

```typescript
const mask = ((1n << BigInt(prefix)) - 1n) << BigInt((isIPv4 ? 32 : 128) - prefix)

cidrRules.push([isIPv4, addr & mask, mask] as [boolean, bigint, bigint])
```
Sources: [src/middleware/ip-restriction/index.ts:111-113](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L111-L113)

### Execution Walkthrough and Sequence

When static rules or CIDR rules are compiled, IP strings pass through a deterministic function chain.

1. `registerStaticRule` receives an IP rule string and invokes `distinctRemoteAddr` to classify it [src/middleware/ip-restriction/index.ts:59-64](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L59-L64).
2. `convertIPv4ToBinary` is called for IPv4 addresses to parse the dotted-decimal string [src/middleware/ip-restriction/index.ts:65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L65).
3. `throwInvalidIPv4Address` is invoked if parsing encounters malformed octets [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150).
4. `createInvalidIPAddressError` generates the typed `TypeError` object carrying `ERR_INVALID_IP_ADDRESS` [src/utils/ipaddr.ts:69-73](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L73).

Sources: [src/middleware/ip-restriction/index.ts:59-65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L59-L65), [src/utils/ipaddr.ts:69-73](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L73), [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150)

```mermaid
sequenceDiagram
    participant Index as ip-restriction/index.ts
    participant IpAddr as utils/ipaddr.ts
    Index->>IpAddr: registerStaticRule() calls convertIPv4ToBinary()
    IpAddr->>IpAddr: parseIPv4ToBinary() validates octets
    IpAddr->>IpAddr: throwInvalidIPv4Address() on malformed input
    IpAddr->>IpAddr: createInvalidIPAddressError() instantiates ERR_INVALID_IP_ADDRESS
```
Sources: [src/middleware/ip-restriction/index.ts:59-65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L59-L65), [src/utils/ipaddr.ts:69-77](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L77), [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150)

## IPv6 Address Normalization and Expansion

### Zero-Compression and Section Expansion

Shortened IPv6 addresses containing double colons (`::`) are expanded to their full canonical 8-section representation by `expandIPv6`. The input string is first split by colons, and if the final section matches an IPv4 format via `IPV4_REGEX`, it is converted to binary and mapped back into hexadecimal IPv6 sections.

```typescript
export const expandIPv6 = (ipV6: string): string => {
  const sections = ipV6.split(':')
  if (IPV4_REGEX.test(sections.at(-1) as string)) {
    sections.splice(
      -1,
      1,
      ...convertIPv6BinaryToString(convertIPv4ToBinary(sections.at(-1) as string)) // => ::7f00:0001
        .substring(2) // => 7f00:0001
        .split(':') // => ['7f00', '0001']
    )
  }
```
Sources: [src/utils/ipaddr.ts:13-23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L13-L23)

The function iterates through each section, padding non-empty nodes to four characters with leading zeros. When an empty string slot is encountered from zero compression, subsequent empty sections are spliced out, and the single slot is replaced with enough `'0000'` blocks to ensure the final array reaches exactly eight sections.

```typescript
  for (let i = 0; i < sections.length; i++) {
    const node = sections[i]
    if (node !== '') {
      sections[i] = node.padStart(4, '0')
    } else {
      // Keep a single empty slot for `::` zero expansion.
      while (sections[i + 1] === '') {
        sections.splice(i + 1, 1)
      }
      sections[i] = new Array(8 - sections.length + 1).fill('0000').join(':')
    }
  }
  return sections.join(':')
}
```
Sources: [src/utils/ipaddr.ts:24-37](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L24-L37)

> [!NOTE]
> The zero-expansion block calculates missing segments dynamically using `8 - sections.length + 1`, inserting the correct number of zero blocks to maintain standard IPv6 length compliance.

Sources: [src/utils/ipaddr.ts:33](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L33)

### Execution Walkthrough and Sequence

When expanding and validating malformed addresses, execution proceeds through a strict error-handling call chain.

1. `expandIPv6` receives a shortened IPv6 address string and delegates nested IPv4-embedded segments to `convertIPv4ToBinary` [src/utils/ipaddr.ts:13-19](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L13-L19).
2. `convertIPv4ToBinary` parses the trailing IPv4 portion and invokes `throwInvalidIPv4Address` if octet bounds or formatting fail [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150).
3. `throwInvalidIPv4Address` immediately calls `createInvalidIPAddressError` with a formatted message [src/utils/ipaddr.ts:75-77](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L75-L77).
4. `createInvalidIPAddressError` instantiates the typed `TypeError` with error code `ERR_INVALID_IP_ADDRESS` [src/utils/ipaddr.ts:69-73](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L73).

Sources: [src/utils/ipaddr.ts:13-19](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L13-L19), [src/utils/ipaddr.ts:69-77](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L77), [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150)

```mermaid
sequenceDiagram
    participant IpAddr as utils/ipaddr.ts
    IpAddr->>IpAddr: expandIPv6() processes sections and checks trailing IPv4
    IpAddr->>IpAddr: convertIPv4ToBinary() parses embedded IPv4
    IpAddr->>IpAddr: throwInvalidIPv4Address() triggers on invalid octets
    IpAddr->>IpAddr: createInvalidIPAddressError() instantiates ERR_INVALID_IP_ADDRESS error
```
Sources: [src/utils/ipaddr.ts:13-19](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L13-L19), [src/utils/ipaddr.ts:69-77](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L69-L77), [src/utils/ipaddr.ts:148-150](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/ipaddr.ts#L148-L150)

## Subnet Matching and Decision Evaluation

### CIDR Rule Compilation and Mask Generation

The `buildMatcher` function processes configured IP rules to construct high-performance lookup structures. When a rule contains CIDR notation detected by `IS_CIDR_NOTATION_REGEX`, the prefix length is validated using `parseCidrPrefix`, ensuring it does not exceed 32 for IPv4 or 128 for IPv6. If the parsed prefix equals the maximum bit length, the rule is treated as a static address and registered directly.

```typescript
const IS_CIDR_NOTATION_REGEX = /\/[^/]*$/
const parseCidrPrefix = (rule: string, prefix: string, max: number): number => {
  if (!/^[0-9]{1,3}$/.test(prefix)) {
    throw new TypeError(`Invalid rule: ${rule}`)
  }
  const parsedPrefix = parseInt(prefix)
  if (parsedPrefix > max) {
    throw new TypeError(`Invalid rule: ${rule}`)
  }
  return parsedPrefix
}
```
Sources: [src/middleware/ip-restriction/index.ts:40-50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L40-L50)

For subnet ranges, bitmasks are computed using BigInt shift operations. When an IPv6 rule represents an IPv4-mapped address with a prefix of 96 or greater, the rule is normalized into an IPv4 CIDR range by subtracting 96 from the prefix length.

```typescript
if (IS_CIDR_NOTATION_REGEX.test(rule)) {
  const separatedRule = rule.split('/')

  const addrStr = separatedRule[0]
  const type = distinctRemoteAddr(addrStr)
  if (type === undefined) {
    throw new TypeError(`Invalid rule: ${rule}`)
  }

  let isIPv4 = type === 'IPv4'
  let prefix = parseCidrPrefix(rule, separatedRule[1], isIPv4 ? 32 : 128)

  if (isIPv4 ? prefix === 32 : prefix === 128) {
    // this rule is a static rule
    rule = addrStr
  } else {
    let addr = (isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary)(addrStr)
    if (type === 'IPv6' && isIPv4MappedIPv6(addr) && prefix >= 96) {
      isIPv4 = true
      addr = convertIPv4MappedIPv6ToIPv4(addr)
      prefix -= 96
    }

    const mask = ((1n << BigInt(prefix)) - 1n) << BigInt((isIPv4 ? 32 : 128) - prefix)

    cidrRules.push([isIPv4, addr & mask, mask] as [boolean, bigint, bigint])
    continue
  }
```
Sources: [src/middleware/ip-restriction/index.ts:88-115](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L88-L115)

> [!WARNING]
> IPv4-mapped IPv6 CIDR rules with prefixes under 96 bits are evaluated under IPv6 rules, whereas prefixes of 96 bits or greater are shifted down and evaluated as IPv4 ranges to maintain correct binary mask alignment.

Sources: [src/middleware/ip-restriction/index.ts:105-109](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L105-L109)

### Evaluation Logic and Runtime Matching

The returned matcher evaluates incoming remote connection objects against compiled static sets, CIDR bitmasks, and custom callback functions in a sequential evaluation chain.

```typescript
return (remote: {
  addr: string
  type: AddressType
  isIPv4: boolean
  binaryAddr?: bigint
}): boolean => {
  if (staticRules.has(remote.addr)) {
    return true
  }
  const remoteAddr = (remote.binaryAddr ||= (
    remote.isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary
  )(remote.addr))
  const remoteIPv4Addr =
    remote.isIPv4 || isIPv4MappedIPv6(remoteAddr)
      ? remote.isIPv4
        ? remoteAddr
        : convertIPv4MappedIPv6ToIPv4(remoteAddr)
      : undefined
  if ((remote.isIPv4 ? staticIPv4Rules : staticIPv6Rules).has(remoteAddr)) {
    return true
  }
  for (const [isIPv4, addr, mask] of cidrRules) {
    if (isIPv4) {
      if (remoteIPv4Addr === undefined) {
        continue
      }
      if ((remoteIPv4Addr & mask) === addr) {
        return true
      }
      continue
    }
    if (remote.isIPv4) {
      continue
    }
    if ((remoteAddr & mask) === addr) {
      return true
    }
  }
  for (const rule of functionRules) {
    if (rule({ addr: remote.addr, type: remote.type })) {
      return true
    }
  }
  return false
}
```
Sources: [src/middleware/ip-restriction/index.ts:122-166](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L122-L166)

| Lookup Target | Internal Structure | Matching Mechanism |
| :--- | :--- | :--- |
| Static String | `staticRules` (`Set<string>`) | Direct string hash lookup against `remote.addr` |
| Static Binary IPv4 | `staticIPv4Rules` (`Set<bigint>`) | BigInt lookup using `convertIPv4ToBinary` |
| Static Binary IPv6 | `staticIPv6Rules` (`Set<bigint>`) | BigInt lookup using `convertIPv6ToBinary` |
| CIDR Subnets | `cidrRules` (`[boolean, bigint, bigint][]`) | Bitwise AND evaluation: `(remoteAddr & mask) === addr` |
| Custom Functions | `functionRules` (`IPRestrictionRuleFunction[]`) | Iterative execution of user-supplied predicate functions |

Sources: [src/middleware/ip-restriction/index.ts:54-58](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L54-L58), [src/middleware/ip-restriction/index.ts:128-164](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L128-L164)

> [!TIP]
> Remote binary addresses are lazily evaluated and cached on the `remote` context object using logical assignment (`remote.binaryAddr ||= ...`), avoiding redundant string-to-bigint parsing during repeated middleware evaluations.

Sources: [src/middleware/ip-restriction/index.ts:131-133](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L131-L133)

## Rejection Handling and Custom Responses

### Execution Flow and Denial Enforcement

When the IP restriction middleware processes an incoming request, access decisions are determined by evaluating the client's address against configured `denyList` and `allowList` rules. If a `denyList` is provided and the client IP matches any rule within it, or if an `allowList` is provided and the client IP fails to match any rule within it, access is denied and an `HTTPException` with status code `403` is thrown.

```typescript
*
* @example
* ```ts
* import { Hono } from 'hono'
* import { ipRestriction } from 'hono/ip-restriction'
* import { getConnInfo } from 'hono/cloudflare-workers'
*
* const app = new Hono()
*
* app.use(
*   '*',
*   ipRestriction(getConnInfo, {
*     // Block a specific IP and an entire subnet
*     denyList: ['192.168.0.5', '10.0.0.0/8'],
*     // Only allow requests from localhost and a private range
*     allowList: ['127.0.0.1', '::1', '192.168.1.0/24'],
*   })
* )
*
* // With a custom error handler
* app.use(
*   '/admin/*',
*   ipRestriction(
*     getConnInfo,
*     { allowList: ['203.0.113.0/24'] },
*     (remote, c) => c.text(`Access denied for ${remote.addr}`, 403)
*   )
* )
*
* app.get('/', (c) => c.text('Hello!'))
* ```
```
Sources: [src/middleware/ip-restriction/index.ts:186-216](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L186-L216)

```mermaid
flowchart TD
    Start([Incoming Request]) --> GetIP[Extract Client IP via getIP]
    GetIP --> CheckDeny{DenyList Match?}
    CheckDeny -->|Yes| Throw403[Throw HTTPException 403]
    CheckDeny -->|No| CheckAllow{AllowList Provided?}
    CheckAllow -->|Yes| CheckAllowMatch{AllowList Match?}
    CheckAllowMatch -->|No| Throw403
    CheckAllowMatch -->|Yes| Proceed["Call next()"]
    CheckAllow -->|No| Proceed
```
Sources: [src/middleware/ip-restriction/index.ts:186-216](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L186-L216)

> [!WARNING]
> If both a `denyList` and an `allowList` are configured, the `denyList` is evaluated first. A match in the deny list immediately triggers a `403` denial before the allow list is even checked.

Sources: [src/middleware/ip-restriction/index.ts:188-198](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L188-L198)

### Custom Block Responses and Error Handling

Developers can customize the response returned upon access denial by catching the `HTTPException` globally in Hono using `app.onError()`. Because the middleware relies on `HTTPException` from `../../http-exception`, catching status `403` allows applications to return custom JSON payloads, HTML error pages, or plain text messages instead of the default framework error response.

```typescript
import { HTTPException } from '../../http-exception'
```
Sources: [src/middleware/ip-restriction/index.ts:8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L8)

> [!TIP]
> To supply a custom block response body, handle `HTTPException` in your Hono application error handler and check `err.status === 403` to render a tailored rejection message.

Sources: [src/middleware/ip-restriction/index.ts:8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/ip-restriction/index.ts#L8)

## Related

- [Secure Headers and CORS](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/security-middleware/secure-headers-and-cors)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
