Search docs

Jump between documentation pages.

Browse docs

IP allow/deny lists

Think of it like…a guest list at the door. Names on the allow list get in; names on the deny list are turned away no matter what — and if the bouncer can't see who you are at all, you don't get in either.

ipRestriction() enforces network-layer access control using IPv4 / IPv6 / CIDR allow- and deny-lists. It is the static counterpart to ipReputation() (dynamic abuse feeds) and geoBlock() (country-level compliance). On reject it throws a ForbiddenError, which DaloyJS renders as RFC 9457 application/problem+json with HTTP 403.

Fails closed by default

Web-standard Request objects do not expose the peer address, so DaloyJS fails closed: unless you tell it how to resolve the client IP, every request is rejected. You opt in either by providing a resolveIp function (reads adapter connection metadata) or by enabling trustProxyHeaders behind a proxy chain you control.

Quick start

ts
import { App, ipRestriction, readRemoteAddress } from "@daloyjs/core";

const app = new App({ trustProxy: true });

app.use(ipRestriction({
  allow: ["10.0.0.0/8", "::1"],
  deny: ["10.6.6.0/24"],
  trustProxyHeaders: true,
}));

At least one of allow or deny must be provided; passing neither throws at construction time.

How matching works

  • Deny wins. When both lists are supplied, the matcher runs deny-first then allow-otherwise. A deny match always loses to nothing — even an explicit allow-list entry cannot override a deny, matching the principle of least privilege.
  • Allow is a whitelist. When allow is set, any peer whose address does not match an entry is rejected with 403.
  • Deny-only. With just a deny list, everything is permitted except the listed ranges.

Resolving the client IP

Behind a trusted proxy chain, set trustProxyHeaders: true to read X-Forwarded-For / X-Real-IP. This defaults to false because those headers are client-spoofable unless every request reaches DaloyJS through infrastructure you control. Pair it with new App({ trustProxy: true }) in production.

ts
import { readRemoteAddress } from "@daloyjs/core";

// Direct (no proxy): read the IP from adapter connection metadata.
app.use(ipRestriction({
  allow: ["203.0.113.0/24"],
  resolveIp: (ctx) => readRemoteAddress(ctx),
}));

// Behind a CDN/load balancer you control:
const app = new App({ trustProxy: true });
app.use(ipRestriction({
  deny: ["192.0.2.0/24"],
  trustProxyHeaders: true,
}));

Customizing the rejection

Override the response body with message. Keep it generic — echoing the client IP back can leak proxy topology to attackers, so the default message deliberately does not include it.

ts
app.use(ipRestriction({
  allow: ["10.0.0.0/8"],
  resolveIp: (ctx) => readRemoteAddress(ctx),
  message: "Access denied from your network.",
}));

When to reach for it

  • Internal admin surfaces reachable only from a VPN or office CIDR range.
  • Partner allow-lists where a fixed set of source ranges may call your API.
  • Hard blocks on a handful of known-bad ranges while keeping a broad allow-list. For evolving threat data, layer ipReputation() on top.