Composition & network
Daloy ships the composition & network slice of the secure-by-default initiative: four primitives that compose the security stack you already have. Every item is opt-in; no existing behaviour changes unless you call the new helper.
1. rateLimit({ groupId }) shared buckets
Every rateLimit() call that declares the same groupId shares one in-memory bucket. Use it to enforce a combined limit across related routes (e.g. login, OTP, password reset) without juggling a shared store yourself.
When you supply a custom store, Daloy still prefixes the derived key with `${groupId}:` so two groups cannot collide in a shared Redis backend either.
2. combine primitives — every / some / except
Declarative composition for your Hooks bundles. Use them to package curated security stacks as a single value and drop the fragile if (...) await next() chains.
every(...layers)runs every bundle in order across every lifecycle phase. Forwards CORS / CSRF / session security markers so boot-time guards still see them on the composed bundle.some(...layers)tries each layer'sbeforeHandleuntil one passes. A returnedResponseis treated as a denial — the next layer gets a turn. The first failure wins when every layer rejects, so place the auth scheme whoseWWW-Authenticatechallenge you want clients to see first.except(when, hooks)skips the wrappedbeforeHandlefor matching paths (/health,/public/**,/v1/*/meta) or for any request where the supplied predicate returnstrue. OnlybeforeHandleis gated so shared concerns like request-id propagation keep running.
3. ipRestriction() — CIDR allow / deny
Block or allow requests by source IP or CIDR range. Pairs naturally with trustProxyHeaders: true behind a trusted proxy so the matched address is the real client, not your load balancer. Supports IPv4, IPv6, and IPv4-mapped IPv6 (::ffff:a.b.c.d). deny always wins over allow.
Invalid IP literals, invalid CIDR prefixes, and calls with neither an allow nor deny list throw at construction time — bugs that would otherwise hide until production traffic hits. By default the helper fails closed because Web-standard requests do not expose the peer address. Supply resolveIp if your adapter exposes connection metadata or if you sit behind a CDN that sends the real client through a custom header (cf-connecting-ip, true-client-ip, …).
4. internal: true + app.inject()
Mark a route as internal: true and the public app.fetch(...) entry point returns 404 — existence cannot be probed. The same route runs normally through app.inject(request), which is meant for cron jobs, admin scripts, and integration tests. Internal routes are also excluded from generated OpenAPI by default; pass includeInternal: true to generateOpenAPI()for private admin SDK generation. The framework also filtersAllow headers so a probe with a different method stays a clean 404 rather than a leaky 405.
Opt-out
Every primitive in this slice is additive; nothing changes unless you call the helper. The earlier secure-defaults master opt-out flag still applies if you ever need to disable secure defaults in a development sandbox: