Security
Bad defaults are bugs. DaloyJS separates core-enforced guardrails from first-party security middleware so the dangerous things are blocked by default and the deployment-specific things stay explicit.
Think of it like…a modern car. Seatbelts, airbags, crumple zones, and ABS are built in and armed by default (core guardrails). The route the driver takes, who's allowed in the passenger seat, and whether you need a child seat are decisions you make per trip (first-party middleware). You don't have to wire the airbag yourself, but you do have to pick a destination.
Plain-English analogies for every protection
If the terminology in this page feels abstract, this table maps every major protection to an everyday analogy. Skim it once and the rest of the security docs read a lot faster.
| Protection | Think of it like… |
|---|---|
| Body-size limit | A weight limit on a parcel before the post office accepts it, so one oversized package can't jam the whole sorting room. |
| Prototype-pollution-safe JSON | A customs form that ignores hand-written notes in the margins. Only the printed boxes count, so smugglers can't scribble extra instructions. |
| Header / response splitting guard | An envelope machine that refuses to print address labels with line-breaks in them, so nobody can sneak a second mailing address onto your package. |
| Path-traversal rejection | A library check-out desk that refuses any call number containing "..", you can borrow books, not walk into the staff-only basement. |
| Request timeout | A taxi meter that auto-ends the trip after 30 seconds of no progress, so a passenger who fell asleep can't hold the cab forever. |
| Method 405 (with Allow header) | A receptionist who tells you "this counter only handles deposits and withdrawals" instead of pretending the counter doesn't exist. |
| Production 5xx redaction | A "sorry, we're experiencing issues" sign on a shop window instead of taping the till's error printout to the glass. |
| secureHeaders (CSP, HSTS, X-Frame-Options) | A bouncer who tells every passing browser: "only run scripts from this building, always use HTTPS, and no, you can't stuff this page inside someone else's frame." |
| CSP nonces + Trusted Types | Numbered wristbands handed out fresh every night. Last night's wristband won't get a script onto the dance floor today. |
| cors (explicit allowlist) | A guest list at the door. "Everyone, including credentials" is the same as no guest list, the guard refuses to enforce it. |
| csrf (double-submit cookie) | A coat-check ticket: the doorman gave you one stub, you hand back the matching stub at the counter. A stranger who didn't walk past the doorman can't guess the number. |
| csrf (Fetch-Metadata) | The doorman just asks "did you come in through my front door?" The browser answers truthfully via Sec-Fetch-Site: no ticket needed. |
| rateLimit | A bouncer's clicker. Same person tries to enter 1000 times in a minute? Sit out the next 60 seconds. |
| rateLimit (Redis store) | One shared clicker across every door of the club, so opening more doors doesn't let the same guest sneak in N times. |
| loadShedding | A power grid that browns out non-essential streetlights before the whole city blacks out. |
| loginThrottle | An ATM that swallows your card after three wrong PINs. |
| ipRestriction (CIDR allow/deny) | A gated community guard list of which addresses can drive in or out, only the ranges you wrote down. |
| requestId | A boarding pass number stapled to every step of your journey. When something breaks, every log can be cross-referenced by that one number. |
| bearerAuth / basicAuth | An ID badge swiped at the door. timingSafeEqualmeans the guard reads the whole badge before deciding, even an attacker timing the response can't tell which digit was wrong. |
| jwt / jwk | A passport (JWT) issued by a known embassy (the identity provider). The border officer checks the issuing authority's signature against the embassy's published seals (JWKS), not against the passport itself. |
| requireScopes | Hotel keycards that only open certain floors. A maintenance card doesn't open guest rooms; a guest card doesn't open the rooftop. |
| session (signed cookie + store) | A coat-check ticket. The server keeps the coat; the cookie is the numbered, signed stub the browser hands back to claim it. |
| rotateSession | Re-issuing a new keycard the moment you get promoted, so anyone holding the old one loses access on the spot. |
| fetchGuard (SSRF defense) | A corporate firewall that won't let your office laptop dial internal admin servers, even if a phishing email tries to trick it into hitting the cloud metadata endpoint. |
| compression (BREACH-aware) | Vacuum-sealing parcels for shipping, but never vacuum-sealing anything with a return address visible through the wrap, because a thief watching the truck could measure the bulge and figure out what's inside. |
| etag (private/no-store skip) | A library returns-receipt that's only stamped for public books. Private records get no receipt, so two patrons can't accidentally compare receipts and learn about each other's files. |
| Refuse-to-boot guards | The engine check that won't let the car start if the parking brake is on or the seatbelts are unbuckled. Better to fail in the driveway than at the first intersection. |
| Internal-service preset | Taking off your raincoat indoors. CSRF and same-origin checks are raincoats for the public street; inside a private building (your service mesh) they're useless, but you still lock the safe. |
| WebSocket CSWSH refuse-to-boot | A doorman who refuses to open the back fire-exit unless he can confirm who you are and which street you walked in from. |
| Webhook HMAC verify | A wax seal on a letter. Anyone can write a letter; only the real sender owns the signet ring that makes that exact pattern. |
| fileField magicBytes | Customs opening every "tin of coffee" to confirm it actually smells like coffee, not gunpowder. Filename extensions are stickers; magic bytes are the actual contents. |
| Supply-chain hardening (pnpm, provenance, SBOM) | Tamper-evident seals on every ingredient before it goes into the kitchen, plus a paper trail (provenance) showing exactly which farm grew it. |
minimum-release-age=1440 | A 24-hour fridge quarantine on freshly delivered groceries, long enough that an obviously-poisoned batch gets recalled before it's served. |
ignore-scripts=true | Refusing to run the "please install this companion app" pop-up that ships with the package. Just the food, not the side dish that calls home. |
What the core enforces
These checks happen in App or the runtime adapter itself. Applications get them without calling any middleware.
- 01ingressIncoming requestmethod, path, headers, body
- 02size capBody-size limitContent-Length over cap to 413
- 03parseHardened JSON parsestrips __proto__ / constructor
- 04routeRouter + method check.. segments reject; bad method to 405
- 05handlerYour typed handlerruns under requestTimeoutMs
| Threat | Built-in behavior |
|---|---|
| Body-size DoS | Streamed read, hard cap (default 1 MiB), Content-Length checked first → 413. |
| Prototype pollution | safeJsonParse strips __proto__, constructor, prototype via reviver. |
| Header / response splitting | sanitizeHeaderName / sanitizeHeaderValue reject CRLF + NUL. |
| Path traversal | Router rejects .. segments and // before walking. |
| Slow-loris / hung handlers | requestTimeoutMs aborts handlers (default 30s); Node adapter sets timeouts. |
| Unsupported content types | Routes with body schemas reject non-allowed content-types → 415. |
| Method confusion | Real 405 with Allow header, never a misleading 404. |
| Information disclosure (5xx) | Production mode strips detail from 5xx problem+json automatically. |
First-party security middleware
These are part of DaloyJS and documented together, but they stay explicit because CSP, CORS, rate-limit keys, session secrets, and CSRF rollout are deployment decisions.
The official starters wire these in for you: Node, Bun, and Deno enablesecureHeaders(), requestId(), and rateLimit(); Cloudflare Worker and Vercel enable secureHeaders() andrequestId() plus tighter edge-friendly body and timeout limits.
Recommended by deployment target
Start with the middleware below unless you have a concrete reason not to. The point is not to hide policy behind a boolean flag; it is to make the risky choices explicit and consistent.
| Target | Recommended baseline |
|---|---|
| Node / Bun / Deno API | requestId(), secureHeaders(), rateLimit(), and cors() when the API is cross-origin. |
| Cloudflare Workers | requestId() and secureHeaders() by default; use cors() only when needed, and prefer an external/shared limiter over the in-memory default when traffic spans many isolates. |
| Vercel | requestId() and secureHeaders() by default; add cors() only when needed, and use a shared limiter if you need durable counters across regions. |
| Cookie-authenticated app | Add session() plus csrf() on top of the baseline so mutating routes are protected against cross-site form and fetch attacks. |
| Behind a trusted reverse proxy | Keep the baseline, then configure rateLimit() with an explicit keyGenerator or set trustProxyHeaders: true only after the proxy strips and rewrites forwarding headers. |
csrf() for state-changing routes
Use csrf() to protect mutating endpoints. Two strategies are supported:
- Double-submit cookie (default): sets a token cookie on safe requests, requires the same value on the
x-csrf-tokenheader for unsafe methods, and rejects mismatches with a timing-safe 403. - Fetch Metadata (
strategy: "fetch-metadata") - tokenless protection that relies on the modernSec-Fetch-Siteheader. No cookie round-trip; no HTML rendering coupling. Recommended for new browser-facing apps.
secureHeaders() defaults
If you need a different CSP, want to disable HSTS in local development, or need a looser permissions policy, pass options to secureHeaders() explicitly. The legacy X-XSS-Protection: 0 header is opt-in via xssProtection: true for deployments that want to explicitly disable old browser XSS filters.
CSP with per-request nonces & Trusted Types
secureHeaders() can build the CSP from a directive map and inject a fresh per-request nonce into script-src, script-src-elem, style-src, and style-src-elem, plus emit require-trusted-types-for 'script' for runtime DOM XSS hardening. The nonce is exposed at ctx.state.cspNonce so handlers can render it into <script nonce="..."> tags.
Auth
SQL injection
Daloy doesn't ship a database driver, but the HTTP boundary it does own (strict Zod schemas, hardened JSON parser, body-size caps) shrinks the surface that reaches your repository layer. See SQL injection for the safe vs. unsafe patterns per ORM (Prisma, Drizzle, Kysely, raw drivers), an allowlisting recipe for dynamic ORDER BY, and the grep rules the maintainers use to catch regressions.
Command injection
DaloyJS's runtime is child_process-free by CI gate, so the framework itself cannot shell out. See Command injection for the safe shape of a handler that does need to invoke an external program (execFile + argv array, never exec(`cmd $${input}`)), the Windows BatBadBut footgun, and the grep rules to keep new bugs out at PR time.
Admin panels
Building an admin or customer-success surface on top of DaloyJS? See Secure admin panels for the recommended pattern: internal: true routes, ipRestriction(), strict CSP with per-request nonces, per-admin authentication, login-throttle rateLimit()groups, and structured audit logging, mapped one-to-one to Aikido's public "secure admin panel" checklist.
Supply-chain
DaloyJS is distributed via pnpm for a stricter install model. Scaffolded pnpm apps inherit the install-time controls, while DaloyJS's own repository and the optional GitHub Actions bundle add CI/CD controls against the cache-poisoning, maintainer-phishing, and OIDC token-abuse patterns seen in recent npm incidents.
- Strict isolation: packages cannot reach phantom dependencies.
- Content-addressable store: every byte is hashed and verified.
- Frozen lockfile in CI with
--ignore-scripts: reproducible installs without transitive lifecycle execution. verify-store-integrity, corruption-detecting reads.strict-peer-dependencies, no silent peer mismatches.minimum-release-age=1440, wait 24h before installing fresh releases.ignore-scripts=truewith explicitpnpm.onlyBuiltDependencies: reviewed allowlist for native install scripts.- SHA-pinned GitHub Actions: the optional generated GitHub workflows pin third-party actions to immutable commits, not mutable tags.
- Protected DaloyJS npm publishing: the framework's own packages use a tag-only release workflow, protected environment approval, OIDC trusted publishing, and
--provenance.
If your generated app lives outside GitHub, carry over the portable parts directly and translate the GitHub workflow rules to your CI host. The framework cannot enforce branch protection or runner egress in a private GitLab, Bitbucket, Azure DevOps, or on-prem installation.
Trusted proxies and rate limiting
DaloyJS no longer trusts X-Forwarded-For or X-Real-IP by default when deriving a rate-limit key. Those headers are client-spoofable unless your reverse proxy strips and rewrites them. The default limiter is therefore global until you provide an explicit keyGenerator or opt in to trustProxyHeaders: true behind a trusted proxy.
For credential-entry routes, use loginThrottle() across /login, OTP, and password-reset routes, and wsRateLimit() on related WebSocket upgrades. Both helpers can spend from the same groupId bucket.
Self-hosted docs assets
The built-in docs helpers no longer force a jsDelivr-shaped CSP. You can self-host the Swagger UI or Scalar assets, add a nonce to the bootstrap script, and emit a same-origin CSP for your docs route.
For the full CI/CD and maintainer playbook, read Supply-chain security. Run pnpm audit --prod in CI and before release.
OWASP API Security Top 10 mapping
For a per-item walkthrough of how Daloy addresses every entry in the OWASP API Security Top 10 (2023) plus the cross-cutting best practices (encryption, validation, rate limiting, logging, inventory, third-party API safety), read OWASP API Top 10 mapping.
Reporting a vulnerability
Use GitHub's private vulnerability reporting at github.com/daloyjs/daloy/security/advisories/new with reproduction steps. Do not open a public issue with exploit details.