IP reputation / dynamic denylist feed
DaloyJS ships ipReputation(). Where ipRestriction() enforces a static allow/deny list compiled once at startup, ipReputation() wires pluggable, periodically-refreshed abuse feeds (Tor exit lists, Spamhaus DROP, cloud-abuse ranges, or your own threat intelligence) into the request path without a redeploy.
- Pluggable feeds: any source that yields IP / CIDR strings.
urlFeed()ships for the common case (fetch a newline / Spamhaus-DROP-style list over HTTP). - Periodic refresh: the denylist reloads on an
unref'd timer so stale ranges expire and new ones are picked up automatically. - Fail-open: a denylist is additive defense, never the only gate. If a feed can't be loaded (initial or refresh), traffic is not blocked: the last-known-good list is retained. A feed outage never takes your app down.
It reuses the same SSRF-grade CIDR matcher as ipRestriction(), is dependency-free, and runs on every supported runtime.
- requestResolve client IPsocket value, or X-Forwarded-For when trustProxyHeaders
- Match the compiled CIDR setsame SSRF-grade matcher as ipRestriction()
- Listed -> blocklog mode never blocks; only fires onMatch
- Not listed / unresolved IP -> allowfeed outage retains last-known-good; fail-open
Quick start
Wiring abuse feeds
A feed is anything implementing IpReputationFeed:
urlFeed() covers the common case. It fetches the URL, understands the Spamhaus-DROP-style <cidr> ; <annotation> format, and skips #, ;, and //comment lines. Lines that aren't valid IPs/CIDRs are skipped, so a partially-malformed feed still loads its good rows. Its outbound fetch is SSRF-hardened by default (see the security notes below).
Fail-open semantics
Reputation is layered defense, so an unavailable feed must never block legitimate traffic:
- A failed initial load leaves an empty (permissive) denylist, so requests flow.
- A failed refresh keeps the previous, last-known-good entries for that feed; the other feeds are unaffected.
- An unresolvable client IP is treated as not-listed.
Observe feed health with onError:
Monitor mode
Roll a new feed out in "log" mode first to measure what it would block before you enforce it:
Manual refresh & introspection
ipReputation() returns a controller you can drive directly:
Custom IP resolution
By default the client IP is resolved from the socket-supplied value; set trustProxyHeaders: true to read X-Forwarded-For / X-Real-IP (only behind a proxy you trust to overwrite them), or pass your own resolveIp:
Security notes
- Defense in depth. A denylist complements (never replaces) authentication, rate limiting, and
ipRestriction()allowlists. - Trust your feeds. A compromised feed can deny legitimate clients. Prefer reputable sources and watch
onError/ match volume. - SSRF.
urlFeed()'s outbound fetch is SSRF-hardened by default: it routes through afetchGuard()instance (per-hop redirect re-validation, cloud-metadata and internal IPs blocked), so a compromised feed host can't redirect the request into internal space. OverridefetchImplonly for a non-standard runtime, or withfetchGuard({ allowPrivate: true })for an intentionally internal feed mirror.