A runtime-portable TypeScript web framework with built-in contract-first routing, validation, OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-focused runtime plus supply-chain posture.
One-line API docs. new App({ openapi: { info: ... }, docs: true }) auto-mounts GET /docs (Scalar), GET /openapi.json, and GET /openapi.yaml — the same DX as FastAPI, without leaving TypeScript.
DaloyJS is maintained in the GitHub organization at https://github.com/daloyjs; the canonical framework repository is https://github.com/daloyjs/daloy.
Most backend code is now written with AI. Non-developers describe an app and ship whatever the model produces; engineers let agents install dependencies, run tests, and open PRs. The code works on the happy path and gets deployed within the hour — usually with no body limits, skippable input validation, admin routes left mounted on the public app, and an outbound fetch that will happily call cloud-metadata endpoints. At the same time, the dependency tree itself has become the attack surface: self-replicating npm worms, malicious postinstall scripts, CI cache-poisoning, and slopsquatting — where an attacker pre-registers a package name an AI assistant is likely to hallucinate.
DaloyJS is built for exactly this moment, from two directions at once:
- A secure-by-default runtime. Body limits, prototype-pollution-safe JSON, path-traversal rejection, request timeouts, header-injection guards, real
405s, and RFC 9457 problem+json with prod-mode redaction are on in the constructor — the dangerous things are off when nobody remembered to turn them off. The app also refuses to boot on unsafe configuration (wildcard CORS with credentials, weak session secrets, a state-changing session route with no CSRF protection, unconfiguredX-Forwarded-*in production). - A hardened supply chain.
@daloyjs/coreships zero runtime dependencies, is published with npm provenance and CycloneDX + SPDX SBOMs, and the pnpm posture (ignore-scripts, a 24-hour release-age cooldown, source-verified lockfiles) plus the CIverify:*gates shrink the blast radius of the campaigns making headlines.
The point is that none of this costs you developer experience or portability: you keep contract-first DX in the league of ts-rest, Elysia, and FastAPI, and Hono-grade portability across Node, Bun, Deno, Workers, Vercel, Fastly, and Lambda. The secure path is simply the path of least resistance. See Vibe Coding Security: what DaloyJS already blocks and the security docs.
DaloyJS exists to be the framework you'd build if you took the best ideas from each modern stack:
| You want | Today's best-of | What DaloyJS gives you |
|---|---|---|
| Best OpenAPI ergonomics | FastAPI | OpenAPI 3.1 from a single route definition; docs: true mounts /docs and /openapi.json. |
| Best Vercel / serverless / edge fit | Hono | Web-standard Request → Response core with adapters for Node, Bun, Deno, Cloudflare, Vercel, Fastly, and Lambda. |
| Mature Swagger / docs / ops in Node | Fastify | Encapsulated plugins, structured logger, graceful shutdown, request ids, and lifecycle hooks — all first-party. |
| Modern TS-first DX, Bun acceptable | Elysia | End-to-end typed handlers, typed context, and a typed in-process client — no codegen step required. |
| Best-in-class typed client codegen for any consumer | Hey API | One pnpm gen command emits a fully-typed fetch SDK from your live OpenAPI spec. |
| Contract-first typed client, no codegen | ts-rest | Your route definition is the contract: an in-process typed client with zero codegen, plus OpenAPI 3.1 + a Hey API SDK for consumers that can't import your types. |
| Opinionated DI / module architecture for large teams | NestJS | Plugin encapsulation, register() prefixes, and defineDependency() typed-DI with per-request dedup — no decorators. |
| Minimalist async middleware cascade | Koa | Koa-style Context on a web-standard core, with validation, OpenAPI, errors, and security headers in-box. |
| Services + real-time API framework | FeathersJS | First-party app.ws() with CSWSH refuse-to-boot guards, plus SSE / NDJSON streaming over explicit OpenAPI routes. |
| Battle-tested Node middleware compatibility | Express v5 | Regex-free trie router, schema-validated routes, RFC 9457 problem+json, and refuse-to-boot guards on every runtime. |
| Portable supply-chain hardening for the apps you build | pnpm defaults + a zero-runtime-dep core | Hardened .npmrc, source-verified lockfiles, zero runtime deps, CycloneDX + SPDX SBOM, and npm provenance attestations. |
framework test suite passing · ≥90% line + function coverage / ≥90% branch coverage · typechecks on TypeScript 6 with `strict: true`
runs on Node, Bun, Deno, Cloudflare, Vercel
~12.3M static-route ops/sec · ~1.5M dynamic-route ops/sec on M-class CPU
Each existing stack is excellent at one thing and forces tradeoffs everywhere else:
- Hono is small and portable but OpenAPI is a plugin afterthought.
- Elysia has gorgeous typing but pulls you toward Bun.
- Fastify has the best Node ops story but is Node-only and validation/types/docs are not unified.
- FastAPI has the best docs ergonomics — but it's Python.
- Hey API gives you the best typed client — but you still need a server that produces a clean spec.
- ts-rest gives lovely end-to-end types from a shared contract — but it rides on top of another server (Express/Fastify/Nest/Next), its safety is TypeScript-only, and OpenAPI and security are bring-your-own.
- npm leaves supply-chain protection up to you.
DaloyJS combines the wins:
- Explicit contracts, minimal ceremony. One
app.route({...})is the source of truth for validation, types, OpenAPI, the typed client, and contract tests. - One source of truth for validation, typing, and docs via Standard Schema — Zod 4 / Valibot / ArkType / TypeBox all work, no lock-in.
- Portable core, optional runtime optimizations — the only thing the core knows is
Request → Response. Adapters live at the edge. - Security guardrails by default — bad defaults are bugs. The core enforces body limits, prototype-pollution-safe JSON, path-traversal rejection, request timeouts, content-type checks, and RFC 9457 problem+json errors with prod-mode redaction. First-party middleware covers Helmet-grade headers, CORS, CSRF, rate limits, request ids, and signed-cookie sessions.
- Tooling and inspectability over magic.
app.introspect()is a public API; contract-test runner is built in. - Optimize for large-team maintenance, not only solo-dev speed. Encapsulated plugins, decorators, request ids, structured logger.
For a new DaloyJS project, the recommended path is the official scaffolder:
pnpm create daloy@latest my-api
# or
npm create daloy@latest my-api
# add GitHub Actions + governance files for a company repo
pnpm create daloy@latest my-api --with-ci --code-owner @acme/securitycreate-daloy gives you a working project structure, runtime template selection, docs routes, OpenAPI wiring, production-oriented defaults, and an optional hardened GitHub security bundle without copying code out of the README.
See Scaffold a project for templates and flags.
DaloyJS is distributed via pnpm for supply-chain hygiene and backed by a hardened release pipeline — strict isolation, content-addressable store, deterministic lockfile, no phantom dependencies, SHA-pinned CI actions, npm staged publishing, and provenance attestations.
pnpm add @daloyjs/core zod@^4Zod 4 is the recommended validator for new DaloyJS apps because it is modern, smaller, and Standard-Schema-compatible. DaloyJS still accepts any Standard Schema validator, so teams can use Valibot, ArkType, TypeBox, or another compatible schema library when that better fits their stack.
The repo ships an .npmrc with hardened defaults:
ignore-scripts=true
minimum-release-age=1440
strict-peer-dependencies=true
prefer-frozen-lockfile=true
verify-store-integrity=true
provenance=trueThese defaults block transitive lifecycle scripts, wait 24 hours before resolving freshly published versions, verify the pnpm store, and require provenance on publish. The few dependencies that truly need install-time builds are allowlisted in pnpm-workspace.yaml under allowBuilds (currently esbuild only), and CI runs pnpm verify:lockfile to reject git dependency sources and non-registry tarball URLs in pnpm-lock.yaml.
The same defaults blunt slopsquatting — the supply-chain attack where an AI coding assistant hallucinates a package name (request-promise-native2, @types/fastify-helmet, etc.) and an attacker registers it on npm with a malicious payload. minimum-release-age=1440 refuses to install anything published in the last 24 hours (the typical detect-and-unpublish window), ignore-scripts=true suppresses lifecycle payloads, blockExoticSubdeps: true and pnpm verify:lockfile reject non-registry sources, pnpm verify:known-dep-names (scripts/verify-known-dep-names.ts) refuses any top-level dep name across the workspace that is not on an explicit allowlist (so pnpm add <hallucinated-name> cannot land in any package.json without a one-line diff that forces a name-review checkpoint), and @daloyjs/core's zero-runtime-dep posture means a hallucinated dep cannot transitively land in the published tarball. See SECURITY.md § Slopsquatting for the full mapping.
Run pnpm audit --prod regularly (or pnpm run audit in this repo) — and pnpm install --frozen-lockfile --ignore-scripts in CI.
Daloy ships a CycloneDX 1.5 + SPDX 2.3 SBOM for both @daloyjs/core and create-daloy.
If you want to run the SBOM flow locally, the two commands are:
pnpm gen:sbom
pnpm verify:sbompnpm gen:sbom regenerates the publishable SBOM files for both packages. pnpm verify:sbom checks that the generated SBOMs match the current package manifests and that @daloyjs/core still declares zero runtime dependencies.
You do not need to remember to run those commands manually for CI or publish:
.github/workflows/ci.ymlrunspnpm gen:sbomandpnpm verify:sbomon every push tomainand every PR..github/workflows/release.ymlrerunspnpm gen:sbomandpnpm verify:sbombefore either npm staged-publish job is allowed to proceed.
That means a release will fail before publish if the SBOMs are missing, stale, or inconsistent with package.json.
The workflow stages releases on npm instead of making them installable immediately, so a maintainer still has to review the stage ID and approve it with npm MFA.
For maintainers, the safe rule is: use one publish path per version. Either publish through the protected GitHub release workflow, or publish locally for an exceptional case, but do not do both for the same version.
import { z } from "zod";
import {
App,
NotFoundError,
secureHeaders,
rateLimit,
requestId,
} from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";
const app = new App({ bodyLimitBytes: 1024 * 1024, requestTimeoutMs: 5_000 });
// First-party security middleware — usually three plugins in other frameworks.
app.use(requestId());
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
app.route({
method: "GET",
path: "/books/:id",
operationId: "getBookById",
tags: ["Books"],
request: { params: z.object({ id: z.string() }) },
responses: {
200: {
description: "Found",
body: z.object({ id: z.string(), title: z.string() }),
},
404: { description: "Not found" },
},
handler: async ({ params }) => ({
status: 200,
body: { id: params.id, title: `Book ${params.id}` },
}),
});
serve(app, { port: 3000 });DaloyJS produces a clean OpenAPI 3.1 document with zero plugins, then @hey-api/openapi-ts turns that into a fully typed TypeScript SDK that any consumer (your web app, mobile RN bundle, internal CLI) can drop in.
pnpm gen # writes generated/openapi.json + generated/client/That single command runs the two scripts:
openapi-ts.config.ts:
import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
input: "./generated/openapi.json",
output: { path: "./generated/client", postProcess: ["prettier"] },
plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
});For TypeScript consumers in the same monorepo you can skip codegen entirely and use the in-process typed client:
import { createClient } from "@daloyjs/core/client";
const client = createClient(app, { baseUrl: "http://localhost:3000" });
const r = await client.getBookById({ params: { id: "1" } });
// ^? { status: 200; body: { id: string; title: string } } | { status: 404; ... }Method inference relies on chaining your
app.route(...)calls (new App().route(a).route(b)) and letting TypeScript infer the variable's type. A wideningconst app: Appannotation, a: Appfactory return type, or registering routes as separate statements erases the per-route types and collapses the client to an untyped surface.
FastAPI-style. One line on the App constructor mounts GET /docs, GET /openapi.json, and GET /openapi.yaml for you,
with a strict CSP and CDN-hosted assets:
import { App } from "@daloyjs/core";
const app = new App({
openapi: { info: { title: "My API", version: "1.0.0" } },
docs: true, // mounts GET /docs (Scalar), GET /openapi.json, GET /openapi.yaml
});Use docs: "auto" to mount only when production: false, or the object form for full control:
new App({
openapi: { info: { title: "My API", version: "1.0.0" } },
docs: {
ui: "scalar", // "scalar" (default) | "swagger" | "redoc"
path: "/reference",
openapiPath: "/spec.json",
openapiYamlPath: "/spec.yaml", // or `false` to disable the YAML route
scalar: {
theme: "kepler",
customCss: ":root { --scalar-color-accent: #2563eb; }",
hideTestRequestButton: true,
},
swagger: {
docExpansion: "none",
displayRequestDuration: true,
},
tags: ["Docs"],
},
});Switch UIs with one word. Scalar and Swagger UI include developer request
consoles for authenticated endpoints: Scalar selects the first configured
OpenAPI security scheme by default, and Swagger UI keeps values from the
Authorize dialog across reloads. ui: "redoc" renders Redoc instead, and its
options are forwarded to Redoc.init via docs.redoc; Redoc displays security
requirements but is a read-only reference UI, not a Try It console:
new App({
openapi: { info: { title: "My API", version: "1.0.0" } },
docs: {
ui: "redoc",
redoc: { hideDownloadButtons: true, sortPropsAlphabetically: true },
},
});The scalar option is forwarded to Scalar's HTML API as JSON configuration,
with Daloy keeping the live openapiPath as the source. Use it for themes,
custom CSS, layout, auth defaults, and client visibility without copying the
HTML helper. The swagger option is forwarded to SwaggerUIBundle with Daloy
owning url / dom_id. Redoc spins up a blob: Web Worker for search, so the
auto-mounted /docs page widens its CSP with worker-src 'self' blob: for
ui: "redoc" only — Scalar and Swagger UI keep the tighter default.
Prefer to mount manually? Import the helpers directly:
import { swaggerUiHtml, scalarHtml, redocHtml, htmlResponse } from "@daloyjs/core/docs";
import { generateOpenAPI } from "@daloyjs/core/openapi";The UI is always contract-accurate — never stale. create-daloy templates opt in with docs: true.
If you omit openapi.info.title / info.version, Daloy reads your project's package.json (name, version, description) automatically — no boilerplate. Deno projects without a package.json fall back to deno.json / deno.jsonc. Explicit values always win.
Prefer a factory? createApp(options) is exported as an alias of new App(options).
import { createApp } from "@daloyjs/core";
const app = createApp({ docs: true });daloy dev [entry] delegates to the host runtime's native watch tool, with no extra config:
| Runtime | Spawned command |
|---|---|
| Node | node --import tsx --watch <entry> |
| Bun | bun --hot <entry> |
| Deno | deno run --watch --allow-net --allow-env --allow-read <entry> |
Entry defaults to src/index.ts, src/main.ts, src/server.ts, or src/app.ts. Install tsx as a dev dependency on Node for TypeScript entries.
Pass --runtime <node|bun|deno> to override runtime detection. This is required when running daloy dev from a package.json script on Bun or Deno, because the CLI binary's #!/usr/bin/env node shebang otherwise forces Node detection. The bun-basic template ships "dev": "daloy dev --runtime bun" for this reason.
Some protections are enforced by the App core whenever the relevant request
path is used. Others are first-party middleware so applications can choose the
right CORS policy, rate-limit key, CSP, session secret, or CSRF rollout for their
deployment.
| Threat | Built-in behavior |
|---|---|
| Body-size DoS | Core-enforced streamed read with a hard cap (default 1 MiB); Content-Length checked first. |
| Prototype pollution | Core JSON parser strips __proto__ / constructor / prototype via reviver. |
| Header / response splitting | Core header sanitizers reject CRLF + NUL. |
| Path traversal | Core router rejects .. segments and // before walking. |
| Slow-loris / hung handlers | Core requestTimeoutMs aborts handlers (default 30 s); Node adapter sets requestTimeout + headersTimeout + maxHeaderSize. |
| HTTP/2 Bomb / header-count flood | Core maxHeaderCount rejects requests with more than 100 header fields (431) before routing; Node adapter sets server.maxHeadersCount. See SECURITY.md for the upstream HTTP/2 mitigations. |
| MIME sniffing | First-party secureHeaders() sets X-Content-Type-Options: nosniff; scaffolded apps enable it. |
| Clickjacking | First-party secureHeaders() sets X-Frame-Options: DENY + CSP frame-ancestors 'none'; scaffolded apps enable it. |
| XSS via injected scripts | First-party secureHeaders() provides a strict CSP default-src 'self' baseline; the directives-object form supports per-request nonces and Trusted Types (require-trusted-types-for 'script'). |
| Cross-origin leakage | First-party secureHeaders() sets cross-origin-opener-policy + cross-origin-resource-policy to same-origin; scaffolded apps enable it. |
| CSRF | First-party csrf() ships two strategies: double-submit cookie (default) and Fetch-Metadata (Sec-Fetch-Site-based, tokenless); both with timing-safe verification. |
| Information disclosure (5xx) | Production mode strips detail from 5xx problem+json automatically. |
| Credential timing attacks | First-party timingSafeEqual() helper for tokens & signatures. |
| Brute-force / scraping | First-party rateLimit() with token-bucket + Retry-After; Node/Bun/Deno scaffolded apps enable it. |
| Method confusion | Real 405 with Allow header, not a misleading 404. |
| CORS misconfig | First-party cors() requires an explicit allowlist and throws for * with credentials. |
| Request correlation | First-party requestId() uses cryptographic ids; scaffolded apps enable it. |
| Supply chain (portable) | pnpm scaffolds keep ignore-scripts=true, minimum-release-age=1440, verified store, reproducible lockfile, and pnpm verify:lockfile source verification; every app also installs a zero-runtime-dependency @daloyjs/core published with CycloneDX + SPDX SBOM and npm provenance you can verify on install — regardless of where you host your repo. |
Portable vs. GitHub-only. The runtime protections and the published @daloyjs/core SBOM/provenance travel with every app you scaffold, no matter which CI host you use — GitLab, Bitbucket, Azure DevOps, Jenkins, on-prem, or laptop. The strongest install-time bundle is available when you choose pnpm, because minimum-release-age, blockExoticSubdeps, and the workspace gates are pnpm features. The @daloyjs/core release pipeline itself is separately hardened on GitHub Actions — no pull_request_target, no Actions cache, top-level permissions: {}, step-security/harden-runner, a protected release.yml, npm trusted publishing with --provenance, CodeQL + Opengrep dual SAST, OpenSSF Scorecard, zizmor, Dependabot, and CODEOWNERS — and create-daloy --with-ci ships the app-safe parts as an optional GitHub Actions bundle for teams on GitHub. See SECURITY.md and the supply-chain security docs.
DaloyJS is a resource server (and a toolkit for building a relying party), not an identity provider. Like Hono, Express, Fastify, or ASP.NET Core, it verifies and enforces tokens on each request — it does not ship a login UI, a user database, or an OAuth2 authorization server. It is not an "IdentityServer": it cannot, on its own, do what Duende IdentityServer, Keycloak, or Auth0 do (run login pages, manage clients/consent, mint tokens).
To add login you bring an OpenID Connect provider. It does not have to be Auth0/Okta/Clerk specifically — any standards-compliant IdP works, including managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) and self-hosted open source (Keycloak, Zitadel, Ory, Authentik, Logto, SuperTokens, Dex). Don't build your own authorization server — verify tokens from a vetted one.
- API as a resource server (default): verify JWTs with
jwk()against the provider's JWKS (asymmetric-only algorithm allowlist,issuer/audienceenforced), then authorize per route withrequireScopes(). - Browser app: use the back-end-for-frontend (BFF) pattern — run the
authorization-code + PKCE flow server-side, keep tokens in a
session()cookie (never in JavaScript), and protect mutations withcsrf().
Read Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect
for the full picture, plus the per-provider guides under /docs/auth.
$ pnpm bench
static route lookup 12,363,799 ops/sec
dynamic 4-segment lookup 1,513,983 ops/sec
miss 4,763,878 ops/sec
- Static (no-param) routes resolve via a single
Map.get— ~12M ops/sec. - Dynamic routes walk a trie, O(path-segments) regardless of route count.
- Body parsing is lazy and only runs when a route declares a body schema.
- No regex on the hot path.
For deployments where every millisecond of startup matters (Lambda, Vercel, Cloudflare Workers, Fastly Compute), import App from the deep entry point instead of the barrel:
import { App } from "@daloyjs/core/app"; // ~13 ms faster cold start than "@daloyjs/core"
import { serve } from "@daloyjs/core/node";@daloyjs/core/app resolves to the same App class with the same secure-by-default constructor — secureHeaders, requestId, body limits, request timeouts, fetchGuard, prototype-pollution guards, problem+json redaction, and every other guardrail are still wired automatically. The deep import only skips loading unrelated peripheral modules (jwk, jwt, multipart, websocket, streaming, compression, subdomains, etc.) that the barrel re-exports for convenience. If you use any of those, import them directly from their own subpaths (@daloyjs/core/jwk, @daloyjs/core/multipart, …) so each one is paid for only when used.
Long-lived Node servers will not notice the difference. This is purely a cold-start optimization for serverless.
const res = await app.request("/books/1");
import { runContractTests } from "@daloyjs/core/contract";
const report = await runContractTests(app);
if (!report.ok) process.exit(1);The contract runner verifies that declared examples actually match their schemas, flags duplicate/missing operationIds, dead routes, and accidental body schemas on safe methods.
Gate it in CI two ways: daloy inspect --check <entry> exits non-zero on any error-level issue, or assert report.ok inside your test suite. Every create-daloy template ships a contract-gate test (tests/contract.test.ts, tests/contract_test.ts on Deno) wired into its test task, so scaffolded projects fail CI on a broken contract out of the box. For a localhost-only gate that runs before code leaves your machine, each template also ships an opt-in pre-push hook (.githooks/pre-push, enabled with hooks:install which points core.hooksPath at it); it runs daloy inspect --check on every git push and is bypassable with git push --no-verify.
const usersPlugin = {
name: "users",
register(app) {
app.route({
method: "GET",
path: "/me",
operationId: "me",
responses: { 200: { description: "ok" } },
handler: async () => ({ status: 200, body: { user: "alice" } }),
});
},
};
app.register(usersPlugin, { prefix: "/users", tags: ["Users"] });
await app.ready();import { serve } from "@daloyjs/core/node"; // Node (Heroku, Railway, Render, Fly.io, any PaaS)
import { serve } from "@daloyjs/core/bun"; // Bun
import { serve } from "@daloyjs/core/deno"; // Deno
import { toFetchHandler } from "@daloyjs/core/cloudflare"; // Cloudflare Workers
import {
toFetchHandler as toVercelFetchHandler,
toRouteHandlers,
toWebHandler,
} from "@daloyjs/core/vercel"; // Vercel Node / Edge / Next.js / Netlify Edge
import { installFastlyListener } from "@daloyjs/core/fastly"; // Fastly Compute
import { toLambdaHandler } from "@daloyjs/core/lambda"; // AWS Lambda / Netlify Functions / Lambda Function URLsThe core only ever sees Request → Response. Adapters live at the edge.
- Hey API — typed OpenAPI client codegen: https://heyapi.dev/openapi-ts/get-started
- Hono — portable web-standard router: https://hono.dev/docs/
- Elysia — TS-first DX & typed context: https://elysiajs.com/at-glance.html
- Fastify — production Node web framework: https://fastify.dev/docs/latest/Reference/
- ts-rest — contract-first, RPC-like client/server over REST: https://ts-rest.com/
- pnpm — strict, secure, content-addressable package manager: https://pnpm.io/motivation
- Standard Schema — universal validator interface: https://github.com/standard-schema/standard-schema
- RFC 9457 — Problem Details for HTTP APIs: https://www.rfc-editor.org/rfc/rfc9457
DaloyJS is now in the 1.0.0 beta (1.0.0-beta.2). The public API is feature-complete and stable for the 1.0 line; from 1.0.0 onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the 1.0.0 GA if beta feedback surfaces something. The framework is already in use for production trials.
Release quality bar. Every release ships with ≥90% line + function coverage and ≥90% branch coverage, strict TypeScript, OpenSSF Scorecard, CodeQL + Opengrep dual SAST, zizmor workflow linting, and npm provenance. Coverage was relaxed from a former 100% gate so complex security work isn't blocked chasing throwaway tests for unreachable defensive branches or tsx source-map phantoms; see AGENTS.md for the policy.
- Contract-first routing with Standard Schema validation (Zod 4, Valibot, ArkType, TypeBox) and OpenAPI 3.1 generated from a single source of truth.
- Live OpenAPI 3.1 spec served as both JSON (
GET /openapi.json) and YAML (GET /openapi.yaml) whendocs: true, with a choice of Scalar (default), Swagger UI, or Redoc viadocs.ui, plus Scalar theming/custom CSS/auth defaults viadocs.scalar, Swagger UI options viadocs.swagger(including persisted Authorize credentials), and Redoc options viadocs.redoc. - Zero-config OpenAPI
infoautofill frompackage.json(Node / Bun) ordeno.json/deno.jsonc(Deno); explicitopenapi.infovalues always win. - RFC 7231 + RFC 5789 HTTP-method allowlist enforced inside
app.route()(WebDAV,TRACE,CONNECTrejected at the framework boundary). - AI-friendly route metadata via optional
meta: { examples, extensions, summary, description, tags }; examples are validated against your schemas at build time, surfaced as OpenAPIexamples+x-daloy-*extensions, and dumped asroutes.json/routes.yamlviadaloy inspect --ai. - API lifecycle and breaking-change detection: mark routes
deprecatedor give them asunsetdate to emit RFC 8594Deprecation/Sunsetheaders and anx-sunsetOpenAPI extension, then gate CI withdiffOpenAPI()/ thedaloy diffcommand, which fail on a breaking change versus the last published spec. - In-process test client (
app.request()), contract-test runner (gated in CI viadaloy inspect --checkand shipped as a default test in everycreate-daloytemplate), in-process typed client, and Hey API codegen viapnpm gen.
- Adapters for Node (Heroku, Railway, Render, Fly.io), Bun, Deno, Cloudflare Workers, Vercel Node / Edge / Next.js / Netlify Edge, Fastly Compute, and AWS Lambda / Netlify Functions / Lambda Function URLs.
daloy devwatch loop delegates to the host runtime's native watcher (node --import tsx --watch,bun --hot, ordeno run --watch) with a--runtimeoverride for cross-runtimepackage.jsonscripts.pnpm create daloyscaffolder with Node, Bun, Deno, Cloudflare Worker, and Vercel templates, plus optional--with-ciGitHub Actions / Dependabot / CODEOWNERS / SECURITY.md hardening. The completion summary surfaces official install links (nodejs.org, pnpm.io, bun.sh) for any runtime or package manager your selections need but that is missing fromPATH, and skips a doomed dependency install when the chosen package manager is absent.- Container-first templates:
HEALTHCHECKto/readyz,STOPSIGNAL SIGTERM, non-root user,tinias PID 1. - Generated
deploy.ymlfor container templates signs every pushed GHCR image with Sigstore Cosign (keyless OIDC) and attaches an SPDX SBOM attestation so consumers cancosign verifyandcosign verify-attestation --type spdxjsoninstead of trusting the registry alone. - Pretty
printStartupBanner()/formatStartupBanner()helpers at@daloyjs/core/banner, used by every starter template (TTY +NO_COLOR/FORCE_COLORaware, ASCII fallback for dumb terminals).
- Body limits, prototype-pollution-safe JSON, path-traversal guard, request timeouts, header injection guards.
- Request-smuggling defense: duplicate
Host,Content-Length, andTransfer-Encodingheaders are rejected. ServerandX-Powered-Byheaders stripped by default.- Structured-log redaction defaults for authorization, cookie, password, token, and JWT-shaped values.
secureHeaders()auto-applied; user-installed instances automatically replace the auto one.- Cross-origin state-changing requests rejected with
403unless a route'scors()policy allows the origin. - Production mode strips
detailfrom 5xx problem+json automatically. - Real 405 with
Allowheader instead of a misleading 404. Cache-Control: no-storebaked intoUnauthorizedError/ForbiddenError/TooManyRequestsErrorso every first-party auth 401 / 403 / 429 response is uncacheable.
The framework refuses to start (or to construct) when configuration is unsafe:
- Weak session secrets,
cors({ origin: "*" })with credentials,session()+ state-changing route withoutcsrf(), and unconfiguredX-Forwarded-*in production. secureDefaults: falsein production unlessacknowledgeInsecureDefaults: trueis set, plus a once-per-processerrorlog naming every disabled default.preset: "internal-service"topology preset for service-to-service deployments behind a mesh / sidecar / private network: turns OFF the browser-only guards (autosecureHeaders,corsCrossOriginGuard,csrfboot guard, unconfiguredX-Forwarded-*guard) while keeping every input, parser, credential, SSRF, weak-secret, and refuse-to-boot guard ON. Per-knob options still win, the choice is logged at boot underevent: "security.preset.applied", and the live posture is auditable viaapp.getSecurityPosture().createJwtSigner()/createJwtVerifier()refusealg: "none", accept only an explicit allowlist, refuse HS + JWK combinations, refuse to sign withoutexp, and refuse HS-shaped secrets under 32 bytes (RFC 7518 §3.2).secureHeaders()refuses to construct withframeOptions: falseAND no CSPframe-ancestorsdirective (no clickjacking defense).cors()refusesmethods: ['*']at construction; defaultallowMethodsnarrowed to[GET, HEAD, POST]soPUT/PATCH/DELETEbecome explicit opt-ins.cspReportRoute()refuses non-application/json(415) and refusesmaxBodyBytes > 64 KiBat construction. The default production logger sink omits the parsed report body unlesslogCspReportBodies: trueis set explicitly.session()andcsrf()refuse cookies that violate the__Secure-prefix policy.- Plugin
dependencies: string[]refuse-to-boot when a prerequisite is missing;topoSortExtensions()refuses cycles, and refuses two extensions declaring overlappingresponseHeaderswithout abefore/afterrelationship. app.ws()scans the effective hook stack and refuses-at-registration when header-mutating middleware (secureHeaders(),cors(),csrf(),compression()) is present, unless the handler opts in viaacknowledgeHeaderMutatingMiddleware: true.
secureHeaderswith strict CSP baseline, per-request nonces, Trusted Types (require-trusted-types-for 'script'),frame-ancestors,cross-origin-opener-policy/cross-origin-resource-policy, and reporting endpoints.corswith explicit-allowlist enforcement.csrfwith double-submit cookie (default) and Fetch-Metadata (Sec-Fetch-Site-based, tokenless) strategies; timing-safe verification.rateLimitwith token-bucket +Retry-After, sharedgroupIdbuckets, and a Redis-backed store at@daloyjs/core/rate-limit-redis.loadShedding()event-loop-pressure middleware (auto-503+Retry-After).loginThrottle()credential-entry preset androtateSession()privilege-change session rotation.ipRestriction()with CIDR-aware IPv4 / IPv6 allow / deny lists.combineprimitives:every,some,except.requestId()with cryptographic ids;trustIncoming: falseby default so client-suppliedX-Request-IDheaders cannot poison logs.bearerAuth()andbasicAuth()with per-schemeverify(credentials, ctx)revalidation hooks, typed-contextonAuthSuccesscallback, andCache-Control: no-storeon every 401 challenge.jwk()asymmetric-only JWKS middleware: refusesHS*at construction, cross-checkskidand JWT-vs-JWKalg, requireshttps://JWKS URLs with TTL caching + in-flight-promise dedup, normalizesscope/scp/scopesclaims.requireScopes()with RFC-6750WWW-Authenticate: Bearerchallenge and per-request scope aggregation.session()with signed cookies and pluggable stores.idempotency()withIdempotency-Keyfingerprinting + byte-for-byte response replay, in-flight409,422on key reuse with a different payload, and a pluggableIdempotencyStore(in-memory default) at@daloyjs/core/idempotency.responseCache()server-side body cache (cache-key + TTL withs-maxage/max-ageorchestration, requestno-store/no-cachedirectives, recursion-safe stale-while-revalidate,Vary-aware keying,X-CacheHIT/MISS/STALE marker, pluggableResponseCacheStorein-memory default) at@daloyjs/core/response-cache. Never cachesSet-Cookieorprivate/no-store/no-cacheresponses. Complementsetag()/compression(), which do not cache bodies.paginationQuery()/encodeCursor()/decodeCursor()/buildPageLinks()/buildLinkHeader()cursor-pagination helpers at@daloyjs/core/pagination: opaque base64url cursors (length-capped, prototype-pollution-safe decode →400on tamper), RFC 8288Linkheader emission with CRLF / header-injection guards, and a Standard Schema that validatescursor/limitand auto-wires both into the OpenAPI spec + typed client viatoJSONSchema().app.metrics()+MetricsRegistry/httpMetrics()Prometheus / OpenMetrics exposition at@daloyjs/core/metrics: dependency-free counters / gauges / histograms, RED instrumentation (http_requests_total,http_request_duration_seconds,http_requests_in_flight) plus process gauges, exposition-injection-safe name/label validation, a per-metric cardinality cap, and an opt-in/metricsroute with the same hardened posture asapp.healthcheck()(bearer token +timingSafeEqual, per-IP rate limit, refuse-to-boot unauthenticated in production). The repo ships anexamples/observability/Docker Compose stack that starts a pre-configured Prometheus + Grafana pair (with an auto-provisioned RED + heatmap dashboard) against any local app viadocker compose -f examples/observability/docker-compose.yml up.otelTracing()OpenTelemetry-compatible distributed tracing at@daloyjs/core/tracing: a dependency-freeHooksbundle that opens oneSERVERspan per request, attaches HTTP semantic-convention attributes (http.request.method,url.path,server.address/server.port,http.response.status_code, …), records exceptions + escalates5xxtoERROR, guarantees a singlespan.end(), and exposes the live span onctx.state.otelSpan. Bring any tracer matching the smallTracingTracerinterface (the real@opentelemetry/apiSDK on Node, or a custom exporter on Workers/Deno) plus your own propagator viacontextFromRequestfortraceparentcontinuation — no OTel SDK is forced into your install. Theexamples/observability/stack also runs Jaeger, andexamples/otel-tracing-demo.tsships a ~120-line dependency-free OTLP/HTTP exporter that streams spans straight to it.tenancy()secure-by-default multitenancy at@daloyjs/core/tenancy: a dependency-freeHooksbundle that resolves the calling tenant once per request and exposes it onctx.state.tenant. Pluggable resolution (tenantFromSubdomainPSL-aware,tenantFromHeader,tenantFromPathPrefix,tenantFromClaim, or a custom(ctx) => string, tried in array order). Refuse-unresolved by default (no ambient "default" tenant leak), format-validated ids (rejects key/log-injection + cache-poisoning payloads before they reach a key), no-enumeration404for unknown tenants, and host-spoof-safe subdomain resolution. AtenantScope()key helper drops straight intorateLimitkeyGeneratorandconcurrencyLimit/idempotency/responseCachescopeto partition each per tenant (CWE-524 cross-tenant cache defense). Runnableexamples/multitenancy-demo.ts.resilientFetch()+CircuitBreakeroutbound resilience at@daloyjs/core/fetch-resilience: a dependency-free circuit breaker (closed → open → half-open), retry-with-backoff (exponential + full jitter, idempotent-method/transient-status scoped, honoursRetry-After), and a per-call timeout (AbortController→FetchTimeoutError) designed to layer on top offetchGuard()— anSsrfBlockedErroris a terminal refusal that is never retried and never trips the breaker, so SSRF protection stays intact under the resilience layer.createWebhookSender()+MemoryWebhookDeadLetterSinkoutbound webhook delivery at@daloyjs/core/webhook-delivery: the outbound counterpart toverifyWebhookSignature()— timestamped HMAC-signedPOSTs (webhook-id/webhook-timestamp/webhook-signature, computed over"<timestamp>.<body>"and reused across retries for safe deduping), bounded retry-with-backoff (transient-status + network scoped, honoursRetry-After), per-attempt timeout, and dead-letter semantics. Transport defaults tofetchGuard(), so a subscriber URL pointing at cloud metadata or a private range is refused with a terminalSsrfBlockedError(never retried, dead-lettered once). Zero runtime dependencies.app.cron()+ standaloneSchedulerin-process scheduled tasks at@daloyjs/core/scheduler: a queue-agnostic schedule primitive for periodic housekeeping (cache sweeps, token refresh, reconciliation). Fixed intervals or 5-field cron expressions (lists / ranges / steps / named months & days /@hourly–@yearlyaliases / optional IANAtimeZone), arithmetic cron parsing (no backtracking regex), fixed-rate single-flight (overlapping ticks are skipped, never run concurrently), per-runtimeoutMswithAbortSignal, and graceful-shutdown integration (stop arming → await in-flight → abort after grace). Timers areunref'd.parseCron()/nextCronRun()exported standalone. Zero runtime dependencies.clientCertAuth()mTLS / client-certificate auth at@daloyjs/core/mtls: authenticate zero-trust / service-to-service callers by their TLS client certificate from two sources — native TLS (the Node adapter lazily reads the peer cert off the socket; plain requests pay nothing) or a TLS-terminating proxy (EnvoyX-Forwarded-Client-Certand nginx/HAProxy-style structured headers).requireVerifiedby default, exactallowSubjectCNs/allowIssuerCNs, constant-timeallowFingerprints,allowSANs(SPIFFE/DNS/URI/IP,TYPE:valueor bare), validity-window enforcement, and a custom asyncverify()hook. Missing cert →401problem+json withCache-Control: no-store; any failed check →403(never echoes cert details). The acceptedClientCertificateis stamped onctx.state.parseForwardedClientCert()/normalizePeerCertificate()exported standalone. Zero runtime dependencies.autoBan()adaptive auto-ban (fail2ban-style) at@daloyjs/core/auto-ban: temporarily ban abusive clients after repeated suspicious responses (default401/403/429, configurablewatchStatuses) within a rollingwindowMs. Bans escalate exponentially for repeat offenders (banMs→2×→4×, capped atmaxBanMs) and decay once the client goes quiet. Observes the outgoing status viaonSend(counts failures from any downstream middleware/handler), enforces inbeforeHandle. Secure-by-default identity attribution — refuses to construct withoutkeyGeneratorortrustProxyHeadersso one offender can never ban everyone; unattributable requests are skipped. PluggableAutoBanStore(mirrors therateLimit()store; in-memory default, Redis-able for multi-instance),groupIdsharing across route groups,429/403ban response withRetry-After, andonBan/onStrikehooks. Zero runtime dependencies.botGuard()bot / User-Agent management at@daloyjs/core/bot-guard: the in-app equivalent of Nginx/WAF bot rules. Blocks empty/missingUser-Agent(default on) and known-abusiveUser-Agentstrings /RegExps, and verifies declared crawlers — a request claiming to be Googlebot/Bingbot is confirmed via reverse-DNS + forward-confirm (the method Google and Bing document), so a spoofedUser-Agentcan't impersonate a trusted crawler. ShipsGOOGLEBOT/BINGBOT/WELL_KNOWN_BOTSpresets and accepts customVerifiedBotRules. Allowlist-first (allowUserAgentsbypasses every rule), secure-by-default (verifiedBotsrefuses to construct without an IP source; unverifiable crawlers blocked unlessblockUnverifiableBots: false), subdomain-boundary-safe domain matching, per-IP verification cache to keep DNS off the hot path,mode: "log"monitor mode,onBlockcallback, and a pluggableBotResolver(default lazynode:dns/promises). Zero runtime dependencies.ipReputation()IP reputation / dynamic denylist feed at@daloyjs/core/ip-reputation: wires pluggable, periodically-refreshed abuse feeds (Tor exit lists, Spamhaus DROP, cloud-abuse ranges, or your own threat intel) into the request path without a redeploy, reusing the same SSRF-grade CIDR matcher asipRestriction(). ShipsurlFeed()(fetches newline / Spamhaus-DROP-style lists, skips comment lines, keeps good rows from a partially-malformed feed) plus a customIpReputationFeedinterface. Fail-open by design — a feed that can't be loaded (initial or refresh) never blocks traffic; the last-known-good list is retained per feed. Periodicunref'd refresh,mode: "log"monitor mode,onMatch/onErrorcallbacks, manualrefresh()/stop()/has()/sizecontroller, and pluggable IP resolution (trustProxyHeaders/resolveIp). Zero runtime dependencies.geoBlock()GeoIP / geo-blocking at@daloyjs/core/geo-block: country allow/deny middleware that maps the client IP to an ISO 3166-1 alpha-2 country and rejects (or logs) traffic from countries you don't serve. No bundled GeoIP database and no runtime dependency — supply either an operator-ownedlookupCountry(ip)(a MaxMind /ip2locationreader, or your own table, reusing the trusted-proxyX-Forwarded-For/X-Real-IPIP resolution) or aresolveCountry(ctx)that reads an edge-injected header (CF-IPCountry,CloudFront-Viewer-Country,x-vercel-ip-country). Deny wins over allow (least privilege); allow-lists fail closed on an unknown country while deny-only fails open (overridable viaallowUnknownCountry). Country codes are validated at construction so typos throw instead of silently never matching.mode: "log"monitor mode with anonBlockdecision hook (denied_country/not_in_allowlist/unknown_country), the resolved country stamped onctx.state.geofor allowed requests, and a403problem+json rejection that never echoes the country/IP. Zero runtime dependencies.concurrencyLimit()per-route / per-client concurrency limits + queueing at@daloyjs/core/concurrency-limit: HAProxymaxconn/queue parity at the app layer. Bounds in-flight requests through a surface with a per-bucket semaphore (maxConcurrent), a bounded FIFO queue (maxQueue) with an optionalqueueTimeoutMs, and a fast503+Retry-Afteronce the queue is full or the wait times out. Partition the budget withscope:"global"(default),"route"(permethod + path),"client"(per identity, needstrustProxyHeaders/keyGenerator), or a custom function (undefinedskips limiting, fail-open). Acquires inbeforeHandleand releases inonSend, so slots are freed on success, error, and short-circuit paths alike — never leaked.onRejectobservability hook, configurableretryAfterSeconds/message. Complements themaxConnectionssocket cap andloadShedding(). Zero runtime dependencies. HAProxymaxconn/queue parity at the app layer. Bounds in-flight requests through a surface with a per-bucket semaphore (maxConcurrent), a bounded FIFO queue (maxQueue) with an optionalqueueTimeoutMs, and a fast503+Retry-Afteronce the queue is full or the wait times out. Partition the budget withscope:"global"(default),"route"(permethod + path),"client"(per identity, needstrustProxyHeaders/keyGenerator), or a custom function (undefinedskips limiting, fail-open). Acquires inbeforeHandleand releases inonSend, so slots are freed on success, error, and short-circuit paths alike — never leaked.onRejectobservability hook, configurableretryAfterSeconds/message. Complements themaxConnectionssocket cap andloadShedding(). Zero runtime dependencies.requestDecompression()inbound decompression-bomb guard at@daloyjs/core/request-decompression: core is safe by omission (it never decompresses request bodies), so this is the opt-in middleware for services that must accept compressed uploads. Inflatesgzip/deflatebodies behind two caps enforced during inflation so a zip bomb is aborted before it is fully materialised: an absolutemaxDecompressedBytes(required) and an expansion-ratiomaxRatio(default100), both rejecting with413. The compressed upload itself is bounded bymaxCompressedBytes(default 1 MiB) before a byte is inflated. Unknown, non-allowlisted, runtime-unsupported, or layered (gzip, gzip) encodings are refused415; malformed streams400; bodyless / uncompressed /identity/GET/HEADtraffic passes through untouched. Runs inonRequestand stashes the inflated bytes so schema-validated bodies and raw-body handlers both see the decompressed payload.onBombobservability hook, exporteddecompressRequestBody()for custom flows. Built on web-standardDecompressionStream(brotli excluded — not in the spec). Zero runtime dependencies.waf()opt-in WAF-lite signature/anomaly inbound-inspection middleware at@daloyjs/core/waf: a first-party defense-in-depth layer for teams without an edge WAF (it does not replace ModSecurity / a CDN WAF). Wires DaloyJS' high-confidence injection signatures — SQLi, XSS, NoSQL-operator injection (reusinghasMongoOperatorKeysfor a structural body check), and command injection — into a single scored inbound-inspection pass over the decoded path, the raw + decoded query string, an opt-in header allowlist, and the validated body. Each rule that fires adds an anomalyscore; reachingblockThreshold(default5) rejects with a generic403(block mode) or merely reports viaonMatch(log mode) so operators can tune against real traffic first. Per-rule enable/disable + score overrides, inspection-surface toggles, control-character-stripped log samples, and bounded scanning (maxValueLength/maxBodyNodes) keep a hostile payload from becoming CPU-DoS. The403body never names the rule that fired. Zero runtime dependencies.- Built-in docs UI Subresource Integrity (SRI):
DocsAssetOptionsletsscalarHtml()/swaggerUiHtml()/redocHtml()and thedocs: { assets }auto-mount pin version-exact*Integrityhashes (sha256/sha384/sha512) plus acrossOriginvalue (default"anonymous") on the CDN-loaded Scalar / Swagger UI / Redoc<script>/<link>tags, so a poisoned jsDelivr asset can't execute. Malformed SRI values throw aTypeErrorat startup (browsers ignore unparseableintegrity, so failing loud avoids a false sense of protection); self-hosting the assets via the sameassetsURLs stays supported. Zero runtime dependencies. - HTTP Message Signatures (RFC 9421) at
@daloyjs/core/http-signatures: first-party sign/verify for server-to-server request authentication via the standardSignature/Signature-Inputheaders — complements the inbound-only webhook HMAC andclientCertAuth()mTLS.signMessage()/signRequest()build an RFC 9421 signature base over derived components (@method,@target-uri,@authority,@scheme,@request-target,@path,@query,@query-param,@status) and HTTP fields with Structured-Fields header serialization;verifyMessage()/verifyRequest()and thehttpSignatureAuth()middleware check them. Algorithmshmac-sha256/ed25519/ecdsa-p256-sha256/ecdsa-p384-sha384/rsa-pss-sha512/rsa-v1_5-sha256via WebCrypto (nonode:imports). Secure-by-default verify: a mandatoryalgorithmsallowlist (no implicit "any alg"), optional per-key alg pinning to defeat algorithm-confusion, a requiredcreatedtimestamp with a 300s freshness window,created-in-future /expiresskew rejection, configurablerequiredComponents, a 32-byte raw-HMAC floor, andnoncereplay defense; the middleware answers a missing/invalid signature with401+Cache-Control: no-storeand stamps the verified result onctx.state.httpSignature. Ships RFC 9530contentDigest()/verifyContentDigest()to bind the request body. Zero runtime dependencies. compression()built on web-standardCompressionStream(prefersbr>gzip>deflate), with BREACH-aware always-on guards (skipsSet-Cookie,Authorization, session / CSRF cookies, already-compressed content types),minimumSize: 1024, negative-compression-ratio post-check, no configurablecompressLevelknob (CPU-DoS defense —level: 9is refused at construction), always-onVary: Accept-Encoding, and strong → weak ETag downgrade per RFC 9110 §8.8.3.etag()helper auto-skips onSet-Cookieand private / no-store / no-cacheCache-Control(cross-tenant fingerprinting defense).timing/timingSafeEqualhelpers.fileField({ magicBytes })upload signature checks.ipRestriction(),wsRateLimit(),requirePayloadAuthsecurity-scheme guard.- Zero-knob crypto helpers:
passwordHash/passwordVerifyat@daloyjs/core/hashing,verifyWebhookSignature/signWebhookPayload. fetchGuard()SSRF defaults.
-
WebSocket primitives with the Bun-style handler shape (
open/message/close/drain/error) running on both Node and Bun adapters. -
Typed
app.ws(path, handler)registration; the upgrade listener is only installed when WS routes exist. -
Production WebSocket routes under
secureDefaultsrequire:- a pre-upgrade
beforeUpgradedecision hook or an explicitacknowledgeUnauthenticated: true, AND - an Origin policy (
allowedOrigins: "same-origin"/string[]/ predicate) oracknowledgeCrossOriginUpgrade: true.
This closes the Cross-Site WebSocket Hijacking (CSWSH) class of bug — Storybook's CVE-2026-27148 is the representative case: cookie auth alone does not stop a malicious site from opening an authenticated WS handshake from a victim's browser. The Origin check runs before
beforeUpgradein both adapters. - a pre-upgrade
-
Contract-first AsyncAPI 3.0 generation for
app.ws()surfaces via@daloyjs/core/asyncapi(generateAsyncAPI()/asyncapiToYAML()) anddaloy inspect --asyncapi. Each route becomes a channel (address + path params) with areceiveoperation for inbound client messages and an optionalsendoperation for outbound messages, described via an optional handlermetablock (summary/description/tags/send/receive/operationId). Setasyncapi: true(mirroringdocs: true) to auto-mount an interactive AsyncAPI UI at/asyncapiplus/asyncapi.json+/asyncapi.yaml— the WebSocket counterpart to the Scalar / Swagger / Redoc OpenAPI viewers, served from a CDN with the same SRI + strict-CSP hardening.
- Plugin encapsulation (Fastify-style), decorators, structured logging, request-id propagation.
- Lifecycle events:
onPluginInstalled,onShutdown,onClose. - Connection-draining graceful shutdown with
Connection: closeon503and in-flight responses. crashOnUnhandledRejectiondefault-on in production.app.healthcheck()/app.readinesscheck()primitives with bearer-token auth and per-IP rate limit.disconnectStatusCode: 499default for client-aborted requests.defineConfig({ schema, source })boot-time typed configuration validation.app({ behindProxy })declarative model (replacestrustProxy);behindProxy.hopscollapses to the(N+1)-from-rightmost slot.- Adapter-independent
ConnInfoabstraction:getConnInfo(), lazyctx.remoteAddress,ctx.remotePort. daloy doctorproduction-posture validator with--audit-secretsand--audit-defaults(flags wildcard-credentials CORS, > 24h CORSmaxAge, > 25 MiB blanket body limits, zeroidleTimeoutMsin production, and unsafe opt-ins).- PSL-aware
subdomains()helper with a≤ 90 dayssnapshot guard. - Secure-by-default multitenancy via
tenancy()+tenantScope(): pluggable tenant resolution (subdomain / header / path / JWT claim / custom), refuse-unresolved + format-validated ids + no-enumeration404by default, and a key helper that partitionsrateLimit/concurrencyLimit/idempotency/responseCacheper tenant. defineDependency()typed-DI helper with per-request deduplication.- Scheme-aware
ctx.state.authtyped contract; named, optionally seeded stateful plugins.
- Streaming helpers (SSE + NDJSON), multipart ergonomics, OpenTelemetry-compatible tracing.
- Integration guides for transactional email — AWS SES, SendGrid, Resend, Postmark, Mailgun, Mailtrap — with a common
EmailSenderplugin pattern and runtime-compatibility matrix. - Authentication & authorization guides for AWS Cognito, Microsoft Entra ID (MSAL), Auth0, Okta, and Clerk — with a common bearer-auth plugin, scope / role enforcement, and runtime-compatibility matrix.
A growing suite of static gates runs on every push and PR:
- Parity / governance / runtime-parity / routing-hardening audits:
verify:parity-audits,verify:governance-audits,verify:runtime-parity-audits,verify:routing-hardening-audits. - Source-tree gates:
verify:no-shrinkwrap,verify:no-bin-shadowing,verify:no-native-addons,verify:no-polyfill-cdns(hijacked-CDN IOCs and typosquats),verify:no-redos-patterns,verify:no-encoded-payloads,verify:no-invisible-unicode,verify:no-weak-random,verify:no-unsafe-buffer,verify:no-leaked-credentials,verify:no-vulnerable-sandboxes. - Agent-skill gates:
verify:no-leaky-agent-skills,verify:no-toxic-agent-skills,verify:no-toxic-skills— scanning every agent-instruction surface (SKILL.md,AGENTS.md,copilot-instructions.md,.cursorrules,CLAUDE.md,*.instructions.md,*.prompt.md); the.cursorrules/CLAUDE.mdfilenames cover the TrapDoor crypto-stealer's AI-agent-config prompt-injection persistence (Socket, 2026-05-24). - Agent / editor config-autorun gate:
verify:no-agent-config-autorun— refuses editor / AI-coding-agent config files that auto-execute a command on folder open or session start (VS CodefolderOpentask, Claude/Gemini"type": "command"hook, CursoralwaysApplyrun-a-script rule, apackage.json"test": "node .github/setup.js"hijack, or a loose.github/dropper), covering the Miasma worm's config-injection detonation surface (SafeDep, 2026-06-05). - Dependency gates:
verify:no-runtime-deps,verify:dep-licenses,verify:known-dep-names,verify:lockfile-sources,verify:no-registry-exfiltration,verify:no-remote-exec,verify:no-lifecycle-scripts,verify:runtime-eol(refuses to release on a Node line past its EOL date). - IOC coverage in
verify:no-registry-exfiltrationandverify:lockfile-sourcesfor active campaigns including Beamglea phishing-CDN,naya-flore/nvlore-hscWhatsApp remote-kill-switch, the Toptal GitHub-org hijack,xuxingfengandxlsx-to-json-lhdestructive payloads,react-login-pagekeylogger,@crypto-exploitwallet drainers, Vietnam-Telegram-ban Fastlane typosquats, surveillance-malware packages, the Discord-webhook reconnaissance campaign, thecodexui-androidAI-coding-agent token theft (reads of~/.codex/auth.json/~/.claude/), and npm-package-aliasing dependency-confusion patterns. SECURITY-CONTACTS.mdrotation file with a machine-readable ACTIVE block and<!-- last-exercise: -->marker; the release workflow refuses to publish whengithub.actoris not on the ACTIVE rotation.- Governance floor reaffirmed by audit: top-level
permissions:on every workflow,persist-credentials: falseon everyactions/checkout, 40-hex SHA pinning on every third-partyuses:,step-security/harden-runneron every workflow using third-party actions, and.github/CODEOWNERSon privileged files. - Mandatory hardware-backed 2FA for every contributor with publish access (documented in
SECURITY.md). @daloyjs/coreis published with CycloneDX 1.5 + SPDX 2.3 SBOMs and npm--provenance; the release workflow usesnpm stage publishso the protectednpm-publishGitHub Environment approval is followed by an out-of-bandnpm stage approvestep with maintainer MFA before any version is installable.
- Single-source-of-truth cookie and temporal-claim helpers at
@daloyjs/core/cookieand@daloyjs/core/time-claims. httpError({ status, problem, headers?, res? })factory extracts headers from a customResponseand refuses-at-construction withMessageLeakErrorif the response would leak request-scoped state (Set-Cookie,Server-Timing,X-*-Token, or anyCache-Controlother thanno-store/no-cache). The allowlist isWWW-Authenticate/Proxy-Authenticate/Retry-After/Content-Type/Content-Language(withContent-Lengthaccepted for safety validation but not forwarded).ProblemRenderOptions.contextHeaderslets direct callers ofHttpError.toResponse()get the same Context-merge as the framework boundary.- A self-paced workshop (4-hour and 8-hour tracks) for senior TypeScript / Node developers: contract-first routes, validation, errors, middleware composition, JWT / JWK, sessions, WebSocket upgrades, CSRF / CORS,
fetchGuard()SSRF defaults, OpenAPI tuning, and contract testing. Every exercise is a single self-containedtsx --watchfile with ordered coding steps and reference solutions.
Roadmap and shipped / in-progress checklists live in ROADMAP.md.
DaloyJS is public and MIT-licensed, but contributions-closed. Pull requests from accounts that are not invited maintainers or explicit repository collaborators are closed automatically. Bug reports, feature requests, and security disclosures are very welcome; see CONTRIBUTING.md and SECURITY.md for the channels that are open.
MIT