IP allow/deny lists
IP restrictions evaluate the deny list first, then the allow list. If the middleware cannot resolve a client address, it rejects the request.
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.
Proxy-header resolution is spoof-resistant: the client IP is read from the rightmost X-Forwarded-For entry (the one your immediate proxy actually appended), so an attacker-prepended left entry cannot bypass an allow-list or dodge a deny. Behind a multi-hop chain (CDN → LB → app), declare the hop count with trustedHops (e.g. trustedHops: 2; trustProxyHeaders: true is exactly 1). When the origin itself can be reached (misconfigured firewall, leaked origin IP), set trustedProxies so the peer socket is verified against a CIDR allowlist before any forwarded header is read (see the autoBan note). With no proxy in front and no peer verification, forwarded-header trust is attacker-controlled by definition.
Quick start
At least one of allow or deny must be provided. Passing neither throws at construction time.
How matching works
- ingressRequestresolveIp / trustProxyHeaders
- no IPCannot resolve client IPfail closed to 403 ForbiddenError
- deny firstMatches a deny range?deny wins, even over allow to 403
- allow whitelistOutside the allow list?not whitelisted to 403
- permittedAllowedrequest proceeds
- 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
allowis set, any peer whose address does not match an entry is rejected with403. - Deny-only. With only a
denylist, everything is permitted except the listed ranges.
Resolving the client IP
Behind a trusted proxy chain, set trustProxyHeaders: true or (preferred when the origin can be reached directly) trustedProxies to read X-Forwarded-For / X-Real-IP. This defaults to off because those headers are client-spoofable unless every request reaches DaloyJS through infrastructure you control. Pair it with new App({ trustProxy: true }) in production.
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.
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.