honojs/hono · error · HTTPException

Forbidden

Error message

Forbidden

What it means

Hono's ipRestriction middleware throws a 403 Forbidden error (via blockError) when it cannot determine the client's IP address. The middleware reads connection info (getIP(c)) and if no remote address is resolvable, it fails closed and blocks the request rather than allowing an unidentified client through.

Source

Thrown at src/middleware/ip-restriction/index.ts:242

  ) => Response | Promise<Response>
): MiddlewareHandler => {
  const allowLength = allowList.length

  const denyMatcher = buildMatcher(denyList)
  const allowMatcher = buildMatcher(allowList)

  const blockError = (c: Context): HTTPException =>
    new HTTPException(403, {
      res: c.text('Forbidden', {
        status: 403,
      }),
    })

  return async function ipRestriction(c, next) {
    const connInfo = getIP(c)
    const addr = typeof connInfo === 'string' ? connInfo : connInfo.remote.address
    if (!addr) {
      throw blockError(c)
    }
    const type =
      (typeof connInfo !== 'string' && connInfo.remote.addressType) || distinctRemoteAddr(addr)

    const remoteData = { addr, type, isIPv4: type === 'IPv4' }

    try {
      if (denyMatcher(remoteData)) {
        if (onError) {
          return onError({ addr, type }, c)
        }
        throw blockError(c)
      }
      if (allowMatcher(remoteData)) {
        return await next()
      }
    } catch (e) {
      if (

View on GitHub (pinned to e2740d5a1b)

Solutions

  1. Ensure the runtime adapter provides connection info (e.g. serve({ fetch: app.fetch, port }, (info) => ({ remote: info })) for @hono/node-server / Bun)
  2. Test IP restriction with a real HTTP request instead of app.request()
  3. If behind a trusted proxy, configure getIP to read a forwarded header like x-forwarded-for so an address is always resolvable

Example fix

// before
const app = new Hono()
app.use(ipRestriction(getIPs /* no conninfo configured */))

// after (Node.js)
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 }, (info) => ({ remote: info }))
Defensive patterns

Strategy: validation

Validate before calling

const getConnIP = (c: Context): string | undefined => {
  try {
    const info = c.env?.conninfo ?? getIP(c)
    return typeof info === 'string' ? info : info?.remote?.address
  } catch {
    return undefined
  }
}

Try / catch

app.onError((err, c) => {
  if (err instanceof HTTPException && err.status === 403) {
    return c.text('Access denied', 403)
  }
  throw err
})

Prevention

When it happens

Trigger: Using secureHeaders/ipRestriction middleware where connInfo is undefined or connInfo.remote.address is missing — e.g. running under a runtime or adapter that doesn't provide Hono's conninfo helper (plain Node server without getConnectionInfo, Bun, some edge runtimes), or testing with app.request() where no socket exists.

Common situations: Adding IP restrictions in local dev where req.raw has no connection info; deploying behind a proxy that strips connection data; forgetting to wire an app.getConnectionHelper or use the correct runtime adapter that populates conninfo.

Understand the failure class

Related errors


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/2e24c7c60fa4a6ce. Report an issue: GitHub.