@daloyjs/core 0.29.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/app.d.ts +28 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +148 -8
- package/dist/app.js.map +1 -1
- package/dist/errors.d.ts +89 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +158 -9
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +16 -1
- package/dist/middleware.js.map +1 -1
- package/dist/websocket.d.ts +22 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -380,9 +380,11 @@ What works today, at a glance:
|
|
|
380
380
|
|
|
381
381
|
- Contract-first routing, Standard Schema validation (Zod 4 / Valibot / ArkType / TypeBox), and OpenAPI 3.1 from a single source of truth.
|
|
382
382
|
- 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.
|
|
383
|
-
- Built-in security primitives (body limits, prototype-pollution-safe JSON, path-traversal guard, request timeouts, header injection guards, **duplicate `Host` / `Content-Length` rejection**, **stripped `Server` / `X-Powered-By` headers by default**, **structured-log redaction defaults** for authorization / cookie / password / token / JWT-shaped values, **`secureHeaders()` auto-applied since `0.16.0`** with user-installed overrides automatically replacing the auto instance, **cross-origin state-changing requests rejected with `403` since `0.16.0`** unless a route's `cors()` policy allows the request origin, **refuse-to-boot on weak session secrets, `cors({ origin: "*" })`, `session()` + state-changing route without `csrf()`, and unconfigured `X-Forwarded-*` in production since `0.17.0`**, **connection-draining shutdown with `Connection: close` on `503` and in-flight responses, `crashOnUnhandledRejection` default-on in production, and `app.healthcheck()` / `app.readinesscheck()` primitives with bearer-token auth + per-IP rate limit since `0.18.0`**, **`rateLimit({ groupId })` shared buckets, `combine` primitives `every` / `some` / `except`, `ipRestriction()` with CIDR-aware IPv4/IPv6 allow/deny lists, and `internal: true` route flag + `app.inject()` since `0.19.0`**, **`loadShedding()` event-loop-pressure middleware (auto-`503` + `Retry-After`), `app.cspReportRoute()` rate-limited CSP violation receiver + `secureHeaders({ reportingEndpoints, reportTo })` wiring, `disconnectStatusCode: 499` default for client-aborted requests, and `defineConfig({ schema, source })` boot-time typed configuration validation since `0.20.0`**, **`createJwtSigner()` / `createJwtVerifier()` with `alg`-discipline (no `alg: "none"`, explicit allowlist, HS+JWK refused at construction) + `exp`-required sign refusal, `requireScopes()` with RFC-6750 `WWW-Authenticate: Bearer` challenge + per-request scope aggregation, and `etag()` helper with `Set-Cookie` / `Cache-Control: private | no-store | no-cache` auto-skip (cross-tenant fingerprinting defense) since `0.21.0`**, **`jwk()` asymmetric-only JWKS middleware (refuses `HS*` at construction, `kid` + JWT-vs-JWK `alg` cross-check, `https://` JWKS URL with TTL caching + in-flight-promise dedup, normalizes `scope` / `scp` / `scopes` claims), per-scheme `verify(credentials, ctx)` revalidation hook on `bearerAuth()` / `jwk()`, `basicAuth({ onAuthSuccess })` typed-context callback, and `Cache-Control: no-store` on every first-party auth helper 401 challenge since `0.22.0`**, **`wsRateLimit()` for WebSocket upgrades, `loginThrottle()` credential-entry preset, `rotateSession()` privilege-change session rotation, `fileField({ magicBytes })` upload signature checks, `requirePayloadAuth` security-scheme guard, and WebSocket safe defaults since `0.23.0`**, **`app({ behindProxy })` declarative model (replaces `trustProxy`), adapter-independent `ConnInfo` abstraction (`getConnInfo()` / lazy `ctx.remoteAddress` / `ctx.remotePort`), `daloy doctor` production-posture validator (with `--audit-secrets` + `--no-audit-defaults`), container-first `create-daloy` templates (`HEALTHCHECK` to `/readyz`, `STOPSIGNAL SIGTERM`, non-root user, `tini` PID 1), PSL-aware `subdomains()` helper with `≤ 90 days` snapshot guard, plugin `dependencies: string[]` refuse-to-boot, namespace-protected `decorate({ override })`, plugin extension ordering with `before` / `after` + cycle detection, `behindProxy.hops` collapses to the `(N+1)`-from-rightmost slot, `defineDependency()` typed-DI helper with per-request deduplication, scheme-aware `ctx.state.auth` typed contract, plugin lifecycle encapsulation default of `local`, and required `name` + optional `seed` for stateful plugins since `0.24.0`**, **`compression()` middleware (built on the web-standard `CompressionStream`, prefers `br` > `gzip` > `deflate` and probes runtime support once) with BREACH-aware always-on guards (skip `Set-Cookie` / `Authorization` / session-or-CSRF cookie / already-compressed content types), `minimumSize: 1024` + negative-compression-ratio post-check, no configurable `compressLevel` knob (CPU-DoS defense — `level: 9` is refused at construction), always-on `Vary: Accept-Encoding`, and strong → weak ETag downgrade per RFC 9110 §8.8.3 since `0.25.0`**, **`secureDefaults: false` refuse-to-construct in production (unless `acknowledgeInsecureDefaults: true`) + once-per-process `error` log naming every disabled default, `createJwtSigner()` / `createJwtVerifier()` refuse HS-shaped secrets `< 32` bytes (RFC 7518 §3.2), `secureHeaders()` refuses to construct with both `frameOptions: false` AND no CSP `frame-ancestors` directive (no clickjacking defense), and mandatory hardware-backed 2FA for every contributor with publish access (documented in `SECURITY.md`) since `0.26.0`**, and **single-source-of-truth cookie and temporal-claim helpers (`@daloyjs/core/cookie`, `@daloyjs/core/time-claims`), `session()` / `csrf()` `__Secure-` cookie refuse-to-boot, zero-runtime-dependency CI governance, and secret-comparison CI grep gate since `0.27.0`**, and **pattern-agnostic-framework parity audit suite since `0.28.0`** (`scripts/verify-wave9-audits.ts` static gates wired into CI as `pnpm verify:wave9-audits` — refuses public setters on `request.url` / `request.path` / `request.method`, refuses `ctx.respond = false`-style response-bypass switches, refuses `Referer`-based redirect targets, refuses `AES-CBC` / `SHA-1` / third-party crypto reach inside the cookie helper module, refuses adapter dispatch via `allowInternal: true` / `app.inject()`, and reaffirms the zero-runtime-dependency posture — plus `daloy doctor --audit-defaults` live-config checks that flag wildcard-credentials CORS, > 24 h CORS `maxAge`, > 25 MiB blanket body limits, zero `idleTimeoutMs` in production, `allowUnsafeValidationDetails` / `exposeFrameworkIdentity` / `enableServerTimingInProduction` opt-ins that the public type does not expose), and **zero-runtime-dependency batteries-included parity & governance audit suite since `0.29.0`** (`SECURITY-CONTACTS.md` rotation file with machine-readable ACTIVE block + `<!-- last-exercise: -->` marker, `scripts/verify-wave10-audits.ts` static gates wired into CI as `pnpm verify:wave10-audits` — refuses a missing/stale rotation file, refuses runtime deps on `@daloyjs/core/package.json`, refuses removal of the plugin-prerequisite refuse-to-boot path or the `topoSortExtensions` cycle-detection throw from `src/app.ts`, and reaffirms the governance floor: top-level `permissions:` on every workflow, `persist-credentials: false` on every `actions/checkout`, 40-hex SHA pinning on every third-party `uses:`, `step-security/harden-runner` on every workflow that uses third-party actions, and `.github/CODEOWNERS` on privileged files — plus a release-workflow contributor-rotation refusal step that exits non-zero when `github.actor` is not on the `SECURITY-CONTACTS` ACTIVE rotation) — all guards opt-out via `app({ secureDefaults: false })`) plus first-party middleware (`secureHeaders` with CSP nonce + Trusted Types, `cors`, `rateLimit`, `requestId`, `bearerAuth`, `basicAuth`, `csrf` with **double-submit cookie** + **Fetch-Metadata** strategies, `session`, `timing` / `timingSafeEqual`) and **zero-knob crypto helpers** (`passwordHash` / `passwordVerify` at `@daloyjs/core/hashing`, `verifyWebhookSignature` / `signWebhookPayload`).
|
|
383
|
+
- Built-in security primitives (body limits, prototype-pollution-safe JSON, path-traversal guard, request timeouts, header injection guards, **duplicate `Host` / `Content-Length` rejection**, **stripped `Server` / `X-Powered-By` headers by default**, **structured-log redaction defaults** for authorization / cookie / password / token / JWT-shaped values, **`secureHeaders()` auto-applied since `0.16.0`** with user-installed overrides automatically replacing the auto instance, **cross-origin state-changing requests rejected with `403` since `0.16.0`** unless a route's `cors()` policy allows the request origin, **refuse-to-boot on weak session secrets, `cors({ origin: "*" })`, `session()` + state-changing route without `csrf()`, and unconfigured `X-Forwarded-*` in production since `0.17.0`**, **connection-draining shutdown with `Connection: close` on `503` and in-flight responses, `crashOnUnhandledRejection` default-on in production, and `app.healthcheck()` / `app.readinesscheck()` primitives with bearer-token auth + per-IP rate limit since `0.18.0`**, **`rateLimit({ groupId })` shared buckets, `combine` primitives `every` / `some` / `except`, `ipRestriction()` with CIDR-aware IPv4/IPv6 allow/deny lists, and `internal: true` route flag + `app.inject()` since `0.19.0`**, **`loadShedding()` event-loop-pressure middleware (auto-`503` + `Retry-After`), `app.cspReportRoute()` rate-limited CSP violation receiver + `secureHeaders({ reportingEndpoints, reportTo })` wiring, `disconnectStatusCode: 499` default for client-aborted requests, and `defineConfig({ schema, source })` boot-time typed configuration validation since `0.20.0`**, **`createJwtSigner()` / `createJwtVerifier()` with `alg`-discipline (no `alg: "none"`, explicit allowlist, HS+JWK refused at construction) + `exp`-required sign refusal, `requireScopes()` with RFC-6750 `WWW-Authenticate: Bearer` challenge + per-request scope aggregation, and `etag()` helper with `Set-Cookie` / `Cache-Control: private | no-store | no-cache` auto-skip (cross-tenant fingerprinting defense) since `0.21.0`**, **`jwk()` asymmetric-only JWKS middleware (refuses `HS*` at construction, `kid` + JWT-vs-JWK `alg` cross-check, `https://` JWKS URL with TTL caching + in-flight-promise dedup, normalizes `scope` / `scp` / `scopes` claims), per-scheme `verify(credentials, ctx)` revalidation hook on `bearerAuth()` / `jwk()`, `basicAuth({ onAuthSuccess })` typed-context callback, and `Cache-Control: no-store` on every first-party auth helper 401 challenge since `0.22.0`**, **`wsRateLimit()` for WebSocket upgrades, `loginThrottle()` credential-entry preset, `rotateSession()` privilege-change session rotation, `fileField({ magicBytes })` upload signature checks, `requirePayloadAuth` security-scheme guard, and WebSocket safe defaults since `0.23.0`**, **`app({ behindProxy })` declarative model (replaces `trustProxy`), adapter-independent `ConnInfo` abstraction (`getConnInfo()` / lazy `ctx.remoteAddress` / `ctx.remotePort`), `daloy doctor` production-posture validator (with `--audit-secrets` + `--no-audit-defaults`), container-first `create-daloy` templates (`HEALTHCHECK` to `/readyz`, `STOPSIGNAL SIGTERM`, non-root user, `tini` PID 1), PSL-aware `subdomains()` helper with `≤ 90 days` snapshot guard, plugin `dependencies: string[]` refuse-to-boot, namespace-protected `decorate({ override })`, plugin extension ordering with `before` / `after` + cycle detection, `behindProxy.hops` collapses to the `(N+1)`-from-rightmost slot, `defineDependency()` typed-DI helper with per-request deduplication, scheme-aware `ctx.state.auth` typed contract, plugin lifecycle encapsulation default of `local`, and required `name` + optional `seed` for stateful plugins since `0.24.0`**, **`compression()` middleware (built on the web-standard `CompressionStream`, prefers `br` > `gzip` > `deflate` and probes runtime support once) with BREACH-aware always-on guards (skip `Set-Cookie` / `Authorization` / session-or-CSRF cookie / already-compressed content types), `minimumSize: 1024` + negative-compression-ratio post-check, no configurable `compressLevel` knob (CPU-DoS defense — `level: 9` is refused at construction), always-on `Vary: Accept-Encoding`, and strong → weak ETag downgrade per RFC 9110 §8.8.3 since `0.25.0`**, **`secureDefaults: false` refuse-to-construct in production (unless `acknowledgeInsecureDefaults: true`) + once-per-process `error` log naming every disabled default, `createJwtSigner()` / `createJwtVerifier()` refuse HS-shaped secrets `< 32` bytes (RFC 7518 §3.2), `secureHeaders()` refuses to construct with both `frameOptions: false` AND no CSP `frame-ancestors` directive (no clickjacking defense), and mandatory hardware-backed 2FA for every contributor with publish access (documented in `SECURITY.md`) since `0.26.0`**, and **single-source-of-truth cookie and temporal-claim helpers (`@daloyjs/core/cookie`, `@daloyjs/core/time-claims`), `session()` / `csrf()` `__Secure-` cookie refuse-to-boot, zero-runtime-dependency CI governance, and secret-comparison CI grep gate since `0.27.0`**, and **pattern-agnostic-framework parity audit suite since `0.28.0`** (`scripts/verify-wave9-audits.ts` static gates wired into CI as `pnpm verify:wave9-audits` — refuses public setters on `request.url` / `request.path` / `request.method`, refuses `ctx.respond = false`-style response-bypass switches, refuses `Referer`-based redirect targets, refuses `AES-CBC` / `SHA-1` / third-party crypto reach inside the cookie helper module, refuses adapter dispatch via `allowInternal: true` / `app.inject()`, and reaffirms the zero-runtime-dependency posture — plus `daloy doctor --audit-defaults` live-config checks that flag wildcard-credentials CORS, > 24 h CORS `maxAge`, > 25 MiB blanket body limits, zero `idleTimeoutMs` in production, `allowUnsafeValidationDetails` / `exposeFrameworkIdentity` / `enableServerTimingInProduction` opt-ins that the public type does not expose), and **zero-runtime-dependency batteries-included parity & governance audit suite since `0.29.0`** (`SECURITY-CONTACTS.md` rotation file with machine-readable ACTIVE block + `<!-- last-exercise: -->` marker, `scripts/verify-wave10-audits.ts` static gates wired into CI as `pnpm verify:wave10-audits` — refuses a missing/stale rotation file, refuses runtime deps on `@daloyjs/core/package.json`, refuses removal of the plugin-prerequisite refuse-to-boot path or the `topoSortExtensions` cycle-detection throw from `src/app.ts`, and reaffirms the governance floor: top-level `permissions:` on every workflow, `persist-credentials: false` on every `actions/checkout`, 40-hex SHA pinning on every third-party `uses:`, `step-security/harden-runner` on every workflow that uses third-party actions, and `.github/CODEOWNERS` on privileged files — plus a release-workflow contributor-rotation refusal step that exits non-zero when `github.actor` is not on the `SECURITY-CONTACTS` ACTIVE rotation), and **multi-runtime web-standard ergonomic-framework parity bake-ins since `0.30.0`** (`Cache-Control: no-store` baked into `UnauthorizedError` / `ForbiddenError` / `TooManyRequestsError` so every first-party auth helper 401 / 403 / 429 response is uncacheable, `cspReportRoute()` refuses `application/json` with `415` and refuses `maxBodyBytes > 64 KiB` at construction with the default production logger sink omitting the parsed report body unless `logCspReportBodies: true` is set explicitly (PII defense — CSP reports include the violated source URL), `cors()` `allowMethods` default narrowed to `[GET, HEAD, POST]` with `methods: ['*']` refused at construction (`PUT` / `PATCH` / `DELETE` are now explicit opt-ins), and `scripts/verify-wave11-audits.ts` wired into CI as `pnpm verify:wave11-audits` covering all of the above plus the reverse-proxy-helper absence audit and Wave 7 compression skip-already-encoded reaffirm), and **mature-Node ergonomic-framework second-pass bake-ins since `0.31.0`** (`useSemicolonDelimiter: false` router-level audit so `/users/42;admin=true` cannot smuggle attacker-controlled query data past auth / CSRF / rate-limit middleware via a reverse-proxy / origin disagreement on RFC 3986 path-segment delimiters, `allowErrorHandlerOverride: false` audit so the framework never ships a standalone `setErrorHandler()` / `onError()` class method that could silently overwrite previously-registered error handlers, `requestId()` `trustIncoming: false` default audit so client-supplied `X-Request-ID` headers cannot poison framework logs by default, RFC 7231 + RFC 5789 HTTP-method allowlist now runtime-enforced inside `app.route()` (WebDAV / `TRACE` / `CONNECT` rejected at the framework boundary), `Connection: close` on every response produced during graceful shutdown reaffirmed by audit, and `scripts/verify-wave12-audits.ts` wired into CI as `pnpm verify:wave12-audits` covering all of the above), and **Wave 11 leftover focused slice since `0.32.0`** (`app.ws()` now scans the effective hook stack for header-mutating middleware — `secureHeaders()` / `cors()` / `csrf()` / `compression()` — and refuses-at-registration with a structured error naming both the WebSocket route AND every conflicting middleware unless the handler opts in via `acknowledgeHeaderMutatingMiddleware: true`, new `httpError({ status, problem, headers?, res? })` factory in `@daloyjs/core` extracts headers from a custom `Response` and refuses-at-construction with `MessageLeakError` when the response would leak request-scoped state — `Set-Cookie`, `Server-Timing`, `X-*-Token`, or `Cache-Control` other than `no-store` / `no-cache` all trip the gate, leaving only the `WWW-Authenticate` / `Proxy-Authenticate` / `Retry-After` / `Content-Type` / `Content-Language` allowlist (with `Content-Length` accepted for safety validation but not forwarded), plus a new `ProblemRenderOptions.contextHeaders` extension so direct callers of `HttpError.toResponse()` get the same Context-merge as the framework boundary, and the Wave 6 `PluginExtension` contract now accepts `responseHeaders?: readonly string[]` — `topoSortExtensions()` refuses-at-call when two extensions declare overlapping `responseHeaders` without declaring a `before` / `after` relationship between them so plugin-registration order can no longer non-deterministically pick a winner on a shared response header) — WebSocket public-route exposure can be acknowledged explicitly, while middleware/header-order checks remain registration invariants) plus first-party middleware (`secureHeaders` with CSP nonce + Trusted Types, `cors`, `rateLimit`, `requestId`, `bearerAuth`, `basicAuth`, `csrf` with **double-submit cookie** + **Fetch-Metadata** strategies, `session`, `timing` / `timingSafeEqual`) and **zero-knob crypto helpers** (`passwordHash` / `passwordVerify` at `@daloyjs/core/hashing`, `verifyWebhookSignature` / `signWebhookPayload`).
|
|
384
384
|
- Streaming helpers (SSE + NDJSON), multipart ergonomics, OpenTelemetry-compatible tracing, signed-cookie sessions with pluggable stores, and a Redis-backed rate-limit store at `@daloyjs/core/rate-limit-redis`.
|
|
385
385
|
- WebSocket primitives with the same Bun-style handler shape (`open`/`message`/`close`/`drain`/`error`) running on both Node and Bun adapters, plus typed `app.ws(path, handler)` registration and route-table awareness so the upgrade listener is only installed when WS routes exist.
|
|
386
|
+
- Production WebSocket routes under `secureDefaults` require a pre-upgrade `beforeUpgrade` decision hook or an explicit `acknowledgeUnauthenticated: true` marker; `httpError({ res })` also uses case-insensitive header merging and never forwards a custom response's `Content-Length` onto the rendered `problem+json` body.
|
|
387
|
+
- Registration-conflict guards are deliberate startup invariants: use `acknowledgeHeaderMutatingMiddleware` for reviewed WebSocket/middleware scopes and explicit plugin `before` / `after` ordering for shared `responseHeaders`, rather than expecting `secureDefaults: false` to suppress those checks.
|
|
386
388
|
- Pretty `printStartupBanner()` / `formatStartupBanner()` startup helpers at `@daloyjs/core/banner`, used by every starter template so `pnpm dev` greets you with a colorized boxed panel (TTY + `NO_COLOR` / `FORCE_COLOR` aware, with an ASCII fallback for dumb terminals).
|
|
387
389
|
- In-process test client (`app.request()`), contract-test runner, in-process typed client, and Hey API codegen via `pnpm gen`.
|
|
388
390
|
- One-command watch loop: `daloy dev` delegates to the host runtime's native watcher (`node --import tsx --watch`, `bun --hot`, or `deno run --watch`) with a `--runtime` override for cross-runtime `package.json` scripts.
|
package/dist/app.d.ts
CHANGED
|
@@ -341,6 +341,17 @@ export interface PluginExtension {
|
|
|
341
341
|
before?: readonly string[];
|
|
342
342
|
/** Extension names this one must run after. */
|
|
343
343
|
after?: readonly string[];
|
|
344
|
+
/**
|
|
345
|
+
* Wave 11 — names of response headers this extension mutates. When two
|
|
346
|
+
* extensions declare an overlapping `responseHeaders` entry without
|
|
347
|
+
* declaring an explicit `before` / `after` relationship between them,
|
|
348
|
+
* {@link topoSortExtensions} refuses-at-registration with a structured
|
|
349
|
+
* error — the resulting header value would otherwise depend on the
|
|
350
|
+
* non-deterministic registration order of the plugins.
|
|
351
|
+
*
|
|
352
|
+
* @since 0.32.0
|
|
353
|
+
*/
|
|
354
|
+
responseHeaders?: readonly string[];
|
|
344
355
|
}
|
|
345
356
|
/** Information passed to {@link App.onShutdown} listeners. */
|
|
346
357
|
export interface ShutdownEvent {
|
|
@@ -399,7 +410,9 @@ export interface CspReportRouteOptions {
|
|
|
399
410
|
} | false;
|
|
400
411
|
/**
|
|
401
412
|
* Maximum accepted request body size, in bytes. Default `8192`. Larger
|
|
402
|
-
* bodies are rejected with `413`.
|
|
413
|
+
* bodies are rejected with `413`. Hard upper bound is 65536 bytes
|
|
414
|
+
* (Wave 11) — CSP / Reporting API reports are small; a cap above 64 KiB
|
|
415
|
+
* is rejected at construction time as a DoS-via-report-flood defense.
|
|
403
416
|
*/
|
|
404
417
|
maxBodyBytes?: number;
|
|
405
418
|
/**
|
|
@@ -412,6 +425,18 @@ export interface CspReportRouteOptions {
|
|
|
412
425
|
ip: string | null;
|
|
413
426
|
userAgent: string | null;
|
|
414
427
|
}) => void | Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Wave 11 — when `false` (the default in production), the default logger
|
|
430
|
+
* sink omits the parsed report body and logs only `{ ip, userAgent }`.
|
|
431
|
+
* CSP reports include the URL that violated CSP, which may contain PII
|
|
432
|
+
* (e.g. a session-id query parameter on a navigation that triggered the
|
|
433
|
+
* report). Opt in only when the operator has reviewed their logging
|
|
434
|
+
* pipeline. Ignored when `onReport` is supplied — custom sinks own their
|
|
435
|
+
* own redaction policy.
|
|
436
|
+
*
|
|
437
|
+
* @since 0.30.0
|
|
438
|
+
*/
|
|
439
|
+
logCspReportBodies?: boolean;
|
|
415
440
|
}
|
|
416
441
|
/**
|
|
417
442
|
* Lightweight introspection record produced by {@link App.introspect}. Useful
|
|
@@ -742,8 +767,8 @@ export declare class App {
|
|
|
742
767
|
* when omitted). Returns `204 No Content` so browsers stop retrying.
|
|
743
768
|
*
|
|
744
769
|
* Combine with `secureHeaders({ reportingEndpoints, reportTo })` to wire
|
|
745
|
-
|
|
746
|
-
*
|
|
770
|
+
* the browser to this endpoint. The route is registered as publicly
|
|
771
|
+
* reachable; that is required for the browser
|
|
747
772
|
* Reporting API to send to it.
|
|
748
773
|
*
|
|
749
774
|
*/
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAEjB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAiBxB,OAAO,EAA4B,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAEV,UAAU,EACV,KAAK,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,eAAe,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAIL,KAAK,gCAAgC,EACrC,KAAK,4BAA4B,EAClC,MAAM,WAAW,CAAC;AACnB,OAAO,EAUL,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/app.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createLogger, noopLogger } from "./logger.js";
|
|
|
7
7
|
import { generateOpenAPI, openapiToYAML, } from "./openapi.js";
|
|
8
8
|
import { docsContentSecurityPolicy, scalarHtml, swaggerUiHtml, } from "./docs.js";
|
|
9
9
|
import { secureHeaders as secureHeadersMiddleware, CORS_HOOK_MARKER, CORS_ORIGIN_ALLOW_MARKER, CORS_WILDCARD_ORIGIN_MARKER, CSRF_HOOK_MARKER, REQUIRE_SCOPES_AGGREGATE_KEY, REQUIRE_SCOPES_HOOK_MARKER, SECURE_HEADERS_MARKER, } from "./middleware.js";
|
|
10
|
+
import { COMPRESSION_HOOK_MARKER } from "./compression.js";
|
|
10
11
|
import { SESSION_HOOK_MARKER, SESSION_SECRETS_MARKER, } from "./session.js";
|
|
11
12
|
import { loadShedding as loadSheddingMiddleware } from "./load-shedding.js";
|
|
12
13
|
import { securitySchemeRequiresPayloadAuth } from "./security-schemes.js";
|
|
@@ -54,6 +55,25 @@ const DISABLED_BY_INSECURE_DEFAULTS = Object.freeze([
|
|
|
54
55
|
"cors({ origin: '*' }) refuse-to-boot",
|
|
55
56
|
"anonymous stateful plugin refuse-to-boot",
|
|
56
57
|
]);
|
|
58
|
+
/**
|
|
59
|
+
* Wave 12 item 11: the exact RFC 7231 + RFC 5789 HTTP-method allowlist.
|
|
60
|
+
* The framework refuses any other method at route-registration time so
|
|
61
|
+
* `TRACE`, `CONNECT`, and WebDAV verbs (`MKCOL`, `COPY`, `PROPFIND`,
|
|
62
|
+
* `MOVE`, etc.) cannot bypass the strict Content-Type / body-on-GET /
|
|
63
|
+
* TRACE-and-CONNECT-refusal defaults. The set mirrors the `HttpMethod`
|
|
64
|
+
* union exported from `./types.ts`.
|
|
65
|
+
*
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
const CANONICAL_HTTP_METHODS = new Set([
|
|
69
|
+
"GET",
|
|
70
|
+
"POST",
|
|
71
|
+
"PUT",
|
|
72
|
+
"PATCH",
|
|
73
|
+
"DELETE",
|
|
74
|
+
"HEAD",
|
|
75
|
+
"OPTIONS",
|
|
76
|
+
]);
|
|
57
77
|
const DEFAULTS = {
|
|
58
78
|
bodyLimitBytes: 1024 * 1024,
|
|
59
79
|
requestTimeoutMs: 30_000,
|
|
@@ -681,6 +701,20 @@ export class App {
|
|
|
681
701
|
* @returns This `App` instance for chaining.
|
|
682
702
|
*/
|
|
683
703
|
route(def) {
|
|
704
|
+
// Wave 12 item 11: refuse non-canonical HTTP methods at runtime.
|
|
705
|
+
// The TypeScript `HttpMethod` union already constrains the public
|
|
706
|
+
// surface, but an unsafe cast (or a runtime caller in plain JS)
|
|
707
|
+
// could bypass it. Closes the "framework silently routes WebDAV /
|
|
708
|
+
// TRACE / CONNECT" class of bypass-via-extended-method bugs at the
|
|
709
|
+
// framework boundary.
|
|
710
|
+
if (!CANONICAL_HTTP_METHODS.has(def.method)) {
|
|
711
|
+
throw new Error(`app.route(): HTTP method "${String(def.method)}" is not on ` +
|
|
712
|
+
`the RFC 7231 + RFC 5789 allowlist. Allowed: ` +
|
|
713
|
+
`${[...CANONICAL_HTTP_METHODS].join(", ")}. Custom methods ` +
|
|
714
|
+
`(TRACE, CONNECT, WebDAV verbs, etc.) bypass the strict ` +
|
|
715
|
+
`Content-Type, body-on-GET, and TRACE/CONNECT refusal ` +
|
|
716
|
+
`defaults and are not supported.`);
|
|
717
|
+
}
|
|
684
718
|
if (def.hooks)
|
|
685
719
|
this.assertSecureHookConfig(def.hooks);
|
|
686
720
|
const fullPath = joinPath(this.prefix, def.path);
|
|
@@ -717,10 +751,47 @@ export class App {
|
|
|
717
751
|
*/
|
|
718
752
|
ws(path, handler) {
|
|
719
753
|
const fullPath = joinPath(this.prefix, path);
|
|
754
|
+
const production = this.isProduction();
|
|
755
|
+
const secureDefaults = this.options.secureDefaults !== false;
|
|
720
756
|
const options = normalizeWebSocketOptions(handler, {
|
|
721
|
-
production
|
|
722
|
-
secureDefaults
|
|
757
|
+
production,
|
|
758
|
+
secureDefaults,
|
|
723
759
|
});
|
|
760
|
+
// Wave 11 — pre-upgrade authentication boundary. In production under
|
|
761
|
+
// secureDefaults, a WebSocket route must either make an explicit
|
|
762
|
+
// pre-upgrade decision (`beforeUpgrade`) or acknowledge that the route is
|
|
763
|
+
// intentionally public. This prevents accidental auth in `open()` after
|
|
764
|
+
// the 101 response has already committed the connection.
|
|
765
|
+
if (production &&
|
|
766
|
+
secureDefaults &&
|
|
767
|
+
handler.beforeUpgrade === undefined &&
|
|
768
|
+
handler.acknowledgeUnauthenticated !== true) {
|
|
769
|
+
throw new Error(`app.ws(${JSON.stringify(fullPath)}): production WebSocket routes must ` +
|
|
770
|
+
"authenticate or reject clients before the RFC 6455 upgrade. Add a " +
|
|
771
|
+
"beforeUpgrade hook for authenticated routes, or pass " +
|
|
772
|
+
"{ acknowledgeUnauthenticated: true } for an intentionally public route.");
|
|
773
|
+
}
|
|
774
|
+
// Wave 11 — WebSocket post-upgrade header immutability. Once the RFC
|
|
775
|
+
// 6455 101 handshake has been sent, no further response headers can be
|
|
776
|
+
// added by middleware; mounting header-mutating middleware on a path
|
|
777
|
+
// that also matches a WS route is a documented misconfiguration in
|
|
778
|
+
// upstream frameworks. Refuse-at-registration with a structured error
|
|
779
|
+
// naming both the WS route and the conflicting middleware unless the
|
|
780
|
+
// developer explicitly acknowledges the configuration.
|
|
781
|
+
if (handler.acknowledgeHeaderMutatingMiddleware !== true) {
|
|
782
|
+
const conflicts = detectHeaderMutatingMiddleware([
|
|
783
|
+
this.options.hooks ?? {},
|
|
784
|
+
...this.groupHooks,
|
|
785
|
+
]);
|
|
786
|
+
if (conflicts.length > 0) {
|
|
787
|
+
throw new Error(`app.ws(${JSON.stringify(fullPath)}): ${conflicts.join(", ")} ` +
|
|
788
|
+
"middleware is mounted on a path that matches this WebSocket " +
|
|
789
|
+
"route, but no response headers can be added after the RFC 6455 " +
|
|
790
|
+
"upgrade. Either move the middleware below the WebSocket scope, " +
|
|
791
|
+
"or pass { acknowledgeHeaderMutatingMiddleware: true } after " +
|
|
792
|
+
"confirming the middleware does not run on Upgrade requests.");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
724
795
|
this.webSocketRoutes.add(fullPath, handler, () => ({ ...this.decorations }), options);
|
|
725
796
|
return this;
|
|
726
797
|
}
|
|
@@ -860,14 +931,20 @@ export class App {
|
|
|
860
931
|
* when omitted). Returns `204 No Content` so browsers stop retrying.
|
|
861
932
|
*
|
|
862
933
|
* Combine with `secureHeaders({ reportingEndpoints, reportTo })` to wire
|
|
863
|
-
|
|
864
|
-
*
|
|
934
|
+
* the browser to this endpoint. The route is registered as publicly
|
|
935
|
+
* reachable; that is required for the browser
|
|
865
936
|
* Reporting API to send to it.
|
|
866
937
|
*
|
|
867
938
|
*/
|
|
868
939
|
cspReportRoute(opts = {}) {
|
|
869
940
|
const path = (opts.path ?? "/__csp-report");
|
|
870
941
|
const maxBytes = opts.maxBodyBytes ?? 8192;
|
|
942
|
+
// Wave 11 — refuse a configured cap above 64 KiB so a misconfigured
|
|
943
|
+
// policy cannot turn the receiver into a DoS-via-report-flood amplifier.
|
|
944
|
+
const HARD_MAX = 65536;
|
|
945
|
+
if (!Number.isInteger(maxBytes) || maxBytes <= 0 || maxBytes > HARD_MAX) {
|
|
946
|
+
throw new Error(`cspReportRoute(): maxBodyBytes must be a positive integer <= ${HARD_MAX}.`);
|
|
947
|
+
}
|
|
871
948
|
const rateLimitConfig = opts.rateLimit === false
|
|
872
949
|
? null
|
|
873
950
|
: { limit: 60, windowMs: 60_000, ...(opts.rateLimit ?? {}) };
|
|
@@ -875,6 +952,10 @@ export class App {
|
|
|
875
952
|
? new Map()
|
|
876
953
|
: null;
|
|
877
954
|
const log = this.log;
|
|
955
|
+
// Wave 11 — only log report bodies when explicitly enabled. In
|
|
956
|
+
// production this is opt-in; in development the body is included by
|
|
957
|
+
// default so violations are debuggable.
|
|
958
|
+
const includeReportBody = opts.logCspReportBodies ?? !this.isProduction();
|
|
878
959
|
this.route({
|
|
879
960
|
method: "POST",
|
|
880
961
|
path,
|
|
@@ -901,12 +982,10 @@ export class App {
|
|
|
901
982
|
.trim()
|
|
902
983
|
.toLowerCase();
|
|
903
984
|
if (contentType !== "application/csp-report" &&
|
|
904
|
-
contentType !== "application/reports+json"
|
|
905
|
-
contentType !== "application/json") {
|
|
985
|
+
contentType !== "application/reports+json") {
|
|
906
986
|
throw new UnsupportedMediaTypeError(contentType || "<none>", [
|
|
907
987
|
"application/csp-report",
|
|
908
988
|
"application/reports+json",
|
|
909
|
-
"application/json",
|
|
910
989
|
]);
|
|
911
990
|
}
|
|
912
991
|
const rawBytes = await readBodyLimited(request, maxBytes);
|
|
@@ -931,7 +1010,9 @@ export class App {
|
|
|
931
1010
|
});
|
|
932
1011
|
}
|
|
933
1012
|
else {
|
|
934
|
-
log.warn(
|
|
1013
|
+
log.warn(includeReportBody
|
|
1014
|
+
? { event: "csp.report", ip, userAgent, report: parsed }
|
|
1015
|
+
: { event: "csp.report", ip, userAgent }, "CSP violation report received");
|
|
935
1016
|
}
|
|
936
1017
|
}
|
|
937
1018
|
catch (err) {
|
|
@@ -1670,6 +1751,32 @@ function corsOriginAllowsFromHooks(layers) {
|
|
|
1670
1751
|
}
|
|
1671
1752
|
return allows;
|
|
1672
1753
|
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Wave 11 — detect header-mutating middleware on a WebSocket route's
|
|
1756
|
+
* effective hook stack. Returns a list of human-readable names for any
|
|
1757
|
+
* middleware that would otherwise lose its headers to the post-upgrade
|
|
1758
|
+
* RFC 6455 frame stream.
|
|
1759
|
+
* @internal
|
|
1760
|
+
*/
|
|
1761
|
+
function detectHeaderMutatingMiddleware(layers) {
|
|
1762
|
+
const found = [];
|
|
1763
|
+
for (const hooks of layers) {
|
|
1764
|
+
const record = hooks;
|
|
1765
|
+
// Skip the framework-default auto-secureHeaders bundle — it is
|
|
1766
|
+
// installed by every App and only sets headers that are missing, so it
|
|
1767
|
+
// never conflicts with a WebSocket upgrade in practice.
|
|
1768
|
+
const isAuto = record[AUTO_SECURE_HEADERS_MARKER] === true;
|
|
1769
|
+
if (!isAuto && record[SECURE_HEADERS_MARKER] === true)
|
|
1770
|
+
found.push("secureHeaders()");
|
|
1771
|
+
if (record[CORS_HOOK_MARKER] === true)
|
|
1772
|
+
found.push("cors()");
|
|
1773
|
+
if (record[CSRF_HOOK_MARKER] === true)
|
|
1774
|
+
found.push("csrf()");
|
|
1775
|
+
if (record[COMPRESSION_HOOK_MARKER] === true)
|
|
1776
|
+
found.push("compression()");
|
|
1777
|
+
}
|
|
1778
|
+
return Array.from(new Set(found));
|
|
1779
|
+
}
|
|
1673
1780
|
/**
|
|
1674
1781
|
* Topological sort of plugin extensions (Wave 6 item 10). Refuses-at-call
|
|
1675
1782
|
* on cyclic ordering with a structured error naming the cycle.
|
|
@@ -1724,6 +1831,39 @@ export function topoSortExtensions(exts) {
|
|
|
1724
1831
|
const remaining = Array.from(byName.keys()).filter((n) => !out.some((e) => e.name === n));
|
|
1725
1832
|
throw new Error(`Plugin extension cycle detected among: ${remaining.map((n) => JSON.stringify(n)).join(", ")}.`);
|
|
1726
1833
|
}
|
|
1834
|
+
// Wave 11 — refuse pairs of extensions that mutate the same response
|
|
1835
|
+
// header without declaring a before/after ordering relative to each
|
|
1836
|
+
// other. The resulting header value would otherwise depend on plugin
|
|
1837
|
+
// registration order.
|
|
1838
|
+
for (let i = 0; i < exts.length; i++) {
|
|
1839
|
+
const a = exts[i];
|
|
1840
|
+
const aHeaders = a.responseHeaders;
|
|
1841
|
+
if (!aHeaders || aHeaders.length === 0)
|
|
1842
|
+
continue;
|
|
1843
|
+
const aSet = new Set(aHeaders.map((h) => h.toLowerCase()));
|
|
1844
|
+
for (let j = i + 1; j < exts.length; j++) {
|
|
1845
|
+
const b = exts[j];
|
|
1846
|
+
const bHeaders = b.responseHeaders;
|
|
1847
|
+
if (!bHeaders || bHeaders.length === 0)
|
|
1848
|
+
continue;
|
|
1849
|
+
const overlap = bHeaders
|
|
1850
|
+
.map((h) => h.toLowerCase())
|
|
1851
|
+
.filter((h) => aSet.has(h));
|
|
1852
|
+
if (overlap.length === 0)
|
|
1853
|
+
continue;
|
|
1854
|
+
const declared = (a.before ?? []).includes(b.name) ||
|
|
1855
|
+
(a.after ?? []).includes(b.name) ||
|
|
1856
|
+
(b.before ?? []).includes(a.name) ||
|
|
1857
|
+
(b.after ?? []).includes(a.name);
|
|
1858
|
+
if (!declared) {
|
|
1859
|
+
throw new Error(`Plugin extension header conflict: ${JSON.stringify(a.name)} and ` +
|
|
1860
|
+
`${JSON.stringify(b.name)} both mutate response header(s) ` +
|
|
1861
|
+
`${overlap.map((h) => JSON.stringify(h)).join(", ")} but neither ` +
|
|
1862
|
+
"declares a `before` or `after` relationship to the other. Add the " +
|
|
1863
|
+
"missing ordering to make the merged header value deterministic.");
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1727
1867
|
return out;
|
|
1728
1868
|
}
|
|
1729
1869
|
function securityMarkersFromHooks(layers) {
|