@cosmicdrift/kumiko-server-runtime 0.368.0 → 0.370.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.
Files changed (44) hide show
  1. package/dist/boot/boot-crypto.js +17 -11
  2. package/dist/boot/worker-boot-core.d.ts +1 -1
  3. package/dist/boot-options/billing-option.d.ts +34 -0
  4. package/dist/boot-options/billing-option.js +35 -0
  5. package/dist/boot-options/demo-mode.d.ts +51 -0
  6. package/dist/boot-options/demo-mode.js +64 -0
  7. package/dist/boot-options/file-storage-option.d.ts +30 -0
  8. package/dist/boot-options/file-storage-option.js +52 -0
  9. package/dist/boot-options/kms-mode.d.ts +27 -0
  10. package/dist/boot-options/kms-mode.js +40 -0
  11. package/dist/boot-options/mail-option.d.ts +30 -0
  12. package/dist/boot-options/mail-option.js +44 -0
  13. package/dist/boot-options/option-env-schema.d.ts +7 -0
  14. package/dist/boot-options/option-env-schema.js +113 -0
  15. package/dist/boot-options/process-signals.d.ts +23 -0
  16. package/dist/boot-options/process-signals.js +58 -0
  17. package/dist/boot-options/resolve-boot-options.d.ts +44 -0
  18. package/dist/boot-options/resolve-boot-options.js +88 -0
  19. package/dist/boot-options/seed-shortcuts.d.ts +49 -0
  20. package/dist/boot-options/seed-shortcuts.js +92 -0
  21. package/dist/boot-options/stack-boot-requirements.d.ts +6 -0
  22. package/dist/boot-options/stack-boot-requirements.js +63 -0
  23. package/dist/build-prod-bundle.d.ts +4 -0
  24. package/dist/build-prod-bundle.js +41 -2
  25. package/dist/csp-hashes.d.ts +15 -0
  26. package/dist/csp-hashes.js +46 -0
  27. package/dist/index.d.ts +18 -9
  28. package/dist/index.js +1 -0
  29. package/dist/rate-limit-defaults.d.ts +19 -0
  30. package/dist/rate-limit-defaults.js +13 -0
  31. package/dist/resolve-unsubscribe-wiring.d.ts +17 -0
  32. package/dist/resolve-unsubscribe-wiring.js +24 -0
  33. package/dist/run-bootstrap.d.ts +2 -2
  34. package/dist/run-prod-app-boot-context.d.ts +5 -3
  35. package/dist/run-prod-app-boot-context.js +4 -2
  36. package/dist/run-prod-app-static-files.d.ts +6 -2
  37. package/dist/run-prod-app-static-files.js +22 -1
  38. package/dist/run-prod-app.d.ts +77 -32
  39. package/dist/run-prod-app.js +172 -120
  40. package/dist/run-worker-app.d.ts +2 -2
  41. package/dist/security-headers.d.ts +16 -5
  42. package/dist/security-headers.js +56 -5
  43. package/package.json +18 -9
  44. package/src/changes.json +43 -0
@@ -0,0 +1,24 @@
1
+ import { createUnsubscribeRoutes, } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
+ import { publicOriginOf } from "@cosmicdrift/kumiko-framework/api";
3
+ import { APP_BASE_URL_ENV } from "./resolve-auth-public-origin.js";
4
+ export function resolveUnsubscribeWiring(opts) {
5
+ const { unsubscribe } = opts;
6
+ if (unsubscribe === undefined)
7
+ return { unsubscribeLinks: undefined, unsubscribeRoutes: [] };
8
+ const baseUrl = publicOriginOf(opts.publicOrigin ?? opts.mailBaseUrl ?? opts.envSource[APP_BASE_URL_ENV] ?? "");
9
+ if (baseUrl === undefined) {
10
+ opts.warn(`unsubscribe is enabled but no valid public app URL is known (auth.publicOrigin, auth.mail.baseUrl or ${APP_BASE_URL_ENV}), so mails get no unsubscribe link.`);
11
+ }
12
+ return {
13
+ unsubscribeLinks: baseUrl === undefined
14
+ ? undefined
15
+ : {
16
+ secret: unsubscribe.secret,
17
+ baseUrl,
18
+ ...(unsubscribe.tenantOrigin !== undefined && {
19
+ tenantOrigin: unsubscribe.tenantOrigin,
20
+ }),
21
+ },
22
+ unsubscribeRoutes: createUnsubscribeRoutes({ secret: unsubscribe.secret }),
23
+ };
24
+ }
@@ -1,6 +1,6 @@
1
1
  import { type BootstrapPlan, type BootstrapReport } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
2
- import { type RunProdAppAuthOptions } from "./run-prod-app.js";
3
- import type { RunWorkerAppOptions } from "./run-worker-app.js";
2
+ import { type RunProdAppAuthOptions } from "./run-prod-app.ts";
3
+ import type { RunWorkerAppOptions } from "./run-worker-app.ts";
4
4
  export type RunBootstrapOptions = Omit<RunWorkerAppOptions, "wireComponents" | "includeBundled" | "jobs" | "eventDispatcher" | "metrics"> & BootstrapPlan & {
5
5
  /** The same auth block the app passes to runProdApp (`admin` is ignored).
6
6
  * Needs a mounted invite flow: `auth.invite`, or `auth.mail` with
@@ -8,8 +8,8 @@ import { type ConfigResolver, type FeatureDefinition, type Registry } from "@cos
8
8
  import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
9
9
  import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
10
10
  import type { Redis } from "ioredis";
11
- import { type BootCrypto } from "./boot/boot-crypto.js";
12
- import type { AuthMailOptions, EmailVerificationSetup, InviteSetup, PasswordResetSetup, SignupSetup } from "./run-prod-app.js";
11
+ import { type BootCrypto } from "./boot/boot-crypto.ts";
12
+ import type { AuthMailOptions, EmailVerificationSetup, InviteSetup, PasswordResetSetup, SignupSetup } from "./run-prod-app.ts";
13
13
  export declare function addConfigAccessorFactory<T extends {
14
14
  readonly configResolver?: ConfigResolver;
15
15
  }>(resolved: T, registry: Registry): T;
@@ -43,7 +43,9 @@ type AuthMailNormalizable = {
43
43
  readonly signup?: SignupSetup;
44
44
  readonly invite?: InviteSetup;
45
45
  };
46
- export declare function resolveAuthMail<T extends AuthMailNormalizable>(auth: T, hmacSecret: string, envSource: Record<string, string | undefined>): T & {
46
+ export declare function resolveAuthMail<T extends AuthMailNormalizable>(auth: T, hmacSecret: string, envSource: Record<string, string | undefined>, mailWiring?: {
47
+ readonly mailConfigured?: boolean;
48
+ }): T & {
47
49
  readonly passwordReset?: PasswordResetOptions;
48
50
  readonly emailVerification?: EmailVerificationOptions;
49
51
  };
@@ -185,7 +185,7 @@ function backfillTokenSecrets(auth, tokenSecret) {
185
185
  }),
186
186
  };
187
187
  }
188
- export function resolveAuthMail(auth, hmacSecret, envSource) {
188
+ export function resolveAuthMail(auth, hmacSecret, envSource, mailWiring = {}) {
189
189
  // Runs before the mail/SMTP guards below so an app-supplied block without
190
190
  // hmacSecret is backfilled even without a `mail` block; an explicit secret still wins.
191
191
  const tokenSecret = auth.mail?.hmacSecret ?? hmacSecret;
@@ -196,7 +196,9 @@ export function resolveAuthMail(auth, hmacSecret, envSource) {
196
196
  // blieben sonst 500). Der eigentliche Mail-Versand läuft über delivery
197
197
  // (channel-email), nicht über diesen Transport — er ist nur der Detektor
198
198
  // "ist Mail konfiguriert?".
199
- if (!createSmtpTransportFromEnv(envSource, { fallbackFrom: auth.mail.from ?? "noreply@localhost" })) {
199
+ // An app transport from runProdApp's `mail` option replaces the probe.
200
+ if (mailWiring.mailConfigured !== true &&
201
+ !createSmtpTransportFromEnv(envSource, { fallbackFrom: auth.mail.from ?? "noreply@localhost" })) {
200
202
  return withResolvedSecrets;
201
203
  }
202
204
  const paths = makeAuthPaths(auth.mail.paths);
@@ -1,11 +1,15 @@
1
1
  import { type CachePolicy } from "@cosmicdrift/kumiko-framework/api";
2
- import { type QueryDispatcher } from "./request-bound-system-query.js";
3
- import type { HostDispatchFn, PageHeadResolver } from "./run-prod-app.js";
2
+ import { type QueryDispatcher } from "./request-bound-system-query.ts";
3
+ import type { HostDispatchFn, PageHeadResolver } from "./run-prod-app.ts";
4
+ import type { SecurityHeadersOption } from "./security-headers.ts";
4
5
  export declare function readStaticFile(filePath: string): Promise<{
5
6
  readonly bytes: Uint8Array;
6
7
  readonly mime: string;
7
8
  readonly mtimeMs: number;
8
9
  } | undefined>;
10
+ /** Hashes for the default CSP. Only an app that keeps the default CSP and serves a
11
+ * staticDir needs them; a missing or broken file leaves inline scripts blocked, so boot says so. */
12
+ export declare function resolveCspScriptHashes(staticDir: string | undefined, securityHeaders: SecurityHeadersOption | undefined): Promise<readonly string[]>;
9
13
  export declare function serveDiskFile(req: Request, pathname: string, file: {
10
14
  readonly bytes: Uint8Array;
11
15
  readonly mime: string;
@@ -1,9 +1,10 @@
1
- import { resolve, sep } from "node:path";
1
+ import { join, resolve, sep } from "node:path";
2
2
  import { cachedResponse, computeStrongEtag, computeWeakEtag, createClientIpResolver, } from "@cosmicdrift/kumiko-framework/api";
3
3
  import { resolveAndInjectPageHead } from "@cosmicdrift/kumiko-headless/apex";
4
4
  import { errorCodeOf } from "@cosmicdrift/kumiko-types/type-guards";
5
5
  import { ASSETS_DIR } from "./build-prod-bundle.js";
6
6
  import { BUNDLED_ASSETS_DIST_DIR } from "./bundled-assets.js";
7
+ import { CSP_HASHES_FILE, parseCspHashes } from "./csp-hashes.js";
7
8
  import { buildRequestBoundSystemQuery, } from "./request-bound-system-query.js";
8
9
  import { stripNoRouteMatchHeader, tryHonoFirst } from "./try-hono-first.js";
9
10
  // Static-asset + SPA-fallback serving for runProdApp's HTTP handler. Split
@@ -48,6 +49,26 @@ export async function readStaticFile(filePath) {
48
49
  throw err;
49
50
  }
50
51
  }
52
+ /** Hashes for the default CSP. Only an app that keeps the default CSP and serves a
53
+ * staticDir needs them; a missing or broken file leaves inline scripts blocked, so boot says so. */
54
+ export async function resolveCspScriptHashes(staticDir, securityHeaders) {
55
+ if (staticDir === undefined || securityHeaders === false)
56
+ return [];
57
+ const csp = securityHeaders?.csp;
58
+ if (csp === false || typeof csp === "string")
59
+ return [];
60
+ const file = await readStaticFile(join(staticDir, CSP_HASHES_FILE));
61
+ const result = file ? parseCspHashes(new TextDecoder().decode(file.bytes)) : undefined;
62
+ if (result?.kind === "found")
63
+ return result.scriptSrc;
64
+ const problem = result === undefined
65
+ ? `${CSP_HASHES_FILE} not found in ${staticDir} (written by kumiko build)`
66
+ : `${CSP_HASHES_FILE} is invalid: ${result.kind === "invalid" ? result.problem : ""}`;
67
+ // biome-ignore lint/suspicious/noConsole: boot-time warn, no logger configured this early
68
+ console.warn(`[runProdApp] ${problem}; the default CSP carries no inline-script hashes, so inline scripts in the served HTML are blocked. ` +
69
+ "Rebuild with kumiko build, or set securityHeaders.csp.");
70
+ return [];
71
+ }
51
72
  // Bundled assets are server-side inputs (readBundledAsset), never public URLs.
52
73
  // Compared on the resolved path, not the URL prefix: "//kumiko-bundled-assets/x"
53
74
  // or a case variant on a case-insensitive disk would slip past a prefix check.
@@ -1,7 +1,6 @@
1
1
  /// <reference types="bun-types" preserve="true" />
2
2
  import { type AccountLockoutOptions, type AccountUnlockOptions, type AuthMailLocale, type AuthPaths, type EmailVerificationOptions, type InviteOptions, type PasswordResetOptions, type SignupOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
3
3
  import { type SeedAdminOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
4
- import { type UnsubscribeLinkConfig } from "@cosmicdrift/kumiko-bundled-features/delivery";
5
4
  import { type ExtraRouteDefinition, type KumikoServeEnv, type PostAuthLandingResolver, type SseBroker } from "@cosmicdrift/kumiko-framework/api";
6
5
  import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
7
6
  import { type EffectiveFeaturesResolver, type FeatureDefinition, type ValidateBootOptions } from "@cosmicdrift/kumiko-framework/engine";
@@ -13,12 +12,22 @@ import { type ObservabilityOptions, type ObservabilityProvider } from "@cosmicdr
13
12
  import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
14
13
  import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
15
14
  import { Redis } from "ioredis";
16
- import { type SystemWireDeps } from "./extra-routes-deps.js";
17
- import { type SecurityHeadersOption } from "./security-headers.js";
18
- import { type SeedCreateFileRef } from "./seed-dispatcher.js";
19
- export { buildBunServeOptions } from "./bun-serve-options.js";
20
- export { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.js";
21
- export { staticCachePolicy } from "./run-prod-app-static-files.js";
15
+ import type { BillingBootOption } from "./boot-options/billing-option.ts";
16
+ import type { DemoModeOption } from "./boot-options/demo-mode.ts";
17
+ import type { FileStorageBootOption } from "./boot-options/file-storage-option.ts";
18
+ import type { KmsMode } from "./boot-options/kms-mode.ts";
19
+ import type { MailBootOption } from "./boot-options/mail-option.ts";
20
+ import { type SignalsOption } from "./boot-options/process-signals.ts";
21
+ import type { ProdSeedsConfig } from "./boot-options/seed-shortcuts.ts";
22
+ import { type SystemWireDeps } from "./extra-routes-deps.ts";
23
+ import { type RateLimitOption } from "./rate-limit-defaults.ts";
24
+ import { type UnsubscribeOption } from "./resolve-unsubscribe-wiring.ts";
25
+ import { type SecurityHeadersOption } from "./security-headers.ts";
26
+ import { type SeedCreateFileRef } from "./seed-dispatcher.ts";
27
+ export { buildBunServeOptions } from "./bun-serve-options.ts";
28
+ export { resolveUnsubscribeWiring, type UnsubscribeOption, } from "./resolve-unsubscribe-wiring.ts";
29
+ export { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.ts";
30
+ export { staticCachePolicy } from "./run-prod-app-static-files.ts";
22
31
  export declare function requireEnv(name: string, src?: Record<string, string | undefined>, context?: string): string;
23
32
  /** Wrapper API for the password-reset flow.
24
33
  *
@@ -257,8 +266,13 @@ export type RunProdAppOptions = {
257
266
  readonly maxRequestBodySize?: number;
258
267
  /** Auth-mode: standard features + routes wired, admin seeded. */
259
268
  readonly auth?: RunProdAppAuthOptions;
260
- /** Custom seed functions, run after the admin seed (when auth-mode). */
261
- readonly seeds?: readonly ProdSeedFn[];
269
+ /** Custom seed functions, run after the admin seed (when auth-mode). The
270
+ * object form adds the declarative demo-tenant / demo-admin / sysadmin
271
+ * seeds from the env and takes the functions under `run`; see
272
+ * `ProdSeedsConfig`.
273
+ * @example
274
+ * seeds: { demoTenant: { key: "demo", name: "Demo" }, sysadminFromEnv: true, run: [seedDemoData] } */
275
+ readonly seeds?: readonly ProdSeedFn[] | ProdSeedsConfig;
262
276
  /** Pfad zum seeds-Directory für ES-Operations / Seed-Migrations
263
277
  * (file-basiert wie drizzle-migrate). Wenn gesetzt + KUMIKO_SKIP_ES_OPS
264
278
  * != "1": runProdApp scannt das Verzeichnis nach `<id>.ts` Files,
@@ -353,6 +367,39 @@ export type RunProdAppOptions = {
353
367
  * ist eine Begründung für den Audit-Trail (z.B. "kms rollout pending,
354
368
  * infra#188"). Ohne Flag und ohne `kms` bricht der Boot ab. */
355
369
  readonly allowPlaintextPii?: string;
370
+ /** Short form over `kms` / `blindIndexKey` / `allowPlaintextPii` that wires
371
+ * the subject-key KMS from the env; not combinable with them. Unset keeps
372
+ * today's behavior: no KMS unless `kms` is passed, and the PII boot gate
373
+ * aborts when PII fields exist without one. See `KmsMode`.
374
+ * @example
375
+ * kmsMode: "required" */
376
+ readonly kmsMode?: KmsMode;
377
+ /** App-wide mail transport: mounts `channel-email` and wires the `auth.mail`
378
+ * flows without an `SMTP_HOST` probe. See `MailBootOption`.
379
+ * @example
380
+ * mail: { transport: "smtp-env", from: "Acme <noreply@acme.example>" } */
381
+ readonly mail?: MailBootOption;
382
+ /** File provider for `file-foundation` plus the `FILE_STORAGE_PROVIDER`
383
+ * default. See `FileStorageBootOption`.
384
+ * @example
385
+ * fileStorage: { provider: "auto" } */
386
+ readonly fileStorage?: FileStorageBootOption;
387
+ /** Stripe billing: `subscription-stripe` plus the tier-syncing webhook
388
+ * route. Needs `billing-foundation` and `tier-engine`. See `BillingBootOption`.
389
+ * @example
390
+ * billing: { stripe: { priceToTier: { [env.STRIPE_PRICE_PRO]: "pro" } }, defaultTier: "free" } */
391
+ readonly billing?: BillingBootOption;
392
+ /** Live-demo mode: read-only gate in the fetch chain plus the anonymous
393
+ * `GET /demo-mode` route. See `DemoModeOption`.
394
+ * @example
395
+ * demoMode: { readOnly: env.DEMO_READ_ONLY === "true", notice: { text: { en: "Read-only demo" } } } */
396
+ readonly demoMode?: DemoModeOption;
397
+ /** SIGTERM/SIGINT handling of `listen()`: drains via `stop()` (default
398
+ * timeout 25 s), exits 0, or 1 when the drain fails or times out. `false`
399
+ * leaves the signals to the app.
400
+ * @example
401
+ * signals: { drainTimeoutMs: 10_000 } */
402
+ readonly signals?: SignalsOption;
356
403
  /** Deploy topology. Default `true` (single container): this process runs
357
404
  * HTTP, both job lanes (api + worker) and the event dispatcher (MSP apply)
358
405
  * inline via `createAllInOneEntrypoint`. Worker-lane crons (e.g. the data
@@ -409,10 +456,7 @@ export type RunProdAppOptions = {
409
456
  * a throw or a non-https value fall back to the app origin. The link carries the mail's
410
457
  * locale, so the confirmation pages speak the mail's language. An app reacts to an address
411
458
  * opt-out with an `r.job` triggered on `DeliveryHandlers.unsubscribeAddress`. */
412
- readonly unsubscribe?: {
413
- readonly secret: string;
414
- readonly tenantOrigin?: UnsubscribeLinkConfig["tenantOrigin"];
415
- };
459
+ readonly unsubscribe?: UnsubscribeOption;
416
460
  /** Hook for app-wired co-running components that need the system-write
417
461
  * dispatcher (e.g. wiring a webhook plugin's system-scoped secrets, or
418
462
  * starting a co-running supervisor) — runs after buildServer, before
@@ -425,23 +469,22 @@ export type RunProdAppOptions = {
425
469
  * the fetch-handler directly (Bun.serve isn't available under vitest +
426
470
  * node), then call handle.listen() manually if needed. */
427
471
  readonly autoListen?: boolean;
428
- /** Feature-toggle resolver — durchgereicht an createApiEntrypoint's
429
- * dispatcherOptions. Sprint-8 Tier-Composition: per-Tenant unterschied-
430
- * liche features aktiv via globalFeatureToggleRuntime. Pattern:
431
- * createLateBoundHolder + post-boot runtime.initialize in einem
432
- * seed-fn (db ist erst nach migrations + features ready). */
472
+ /** Per-tenant feature resolver, passed to the dispatcher.
473
+ * @deprecated Removed in the next release. `createTierEngineFeature({ tierMap })`
474
+ * (or `composeSaasStack`) builds the resolver at boot, and prod-only features
475
+ * come from the `mail`, `fileStorage` and `billing` options. */
433
476
  readonly effectiveFeatures?: EffectiveFeaturesResolver;
434
477
  /** Composed Zod-schema for env-validation (from `composeEnvSchema({
435
- * features, extend })` in @cosmicdrift/kumiko-framework/env). When set:
478
+ * core, features, extend })` in @cosmicdrift/kumiko-framework/env):
436
479
  * - `process.env` is parsed against it BEFORE any boot work; missing
437
480
  * or invalid vars throw a `KumikoBootError` listing ALL problems
438
481
  * at once (not first-fail).
439
482
  * - `KUMIKO_DRY_RUN_ENV=human|json|pulumi|k8s` introspects the schema
440
483
  * and prints the env-var inventory, then exits without booting.
441
- *
442
- * 9.1 is additive: features that still read `process.env` directly
443
- * keep working. Migration to the schema is Sprint-9.2-9.5. */
444
- readonly envSchema?: ComposedEnvSchema;
484
+ * - `KUMIKO_DRY_RUN_ENV=boot` prints a per-variable table. With required
485
+ * values missing it exits 0 without validating the feature wiring; an
486
+ * invalid value exits 1; a complete env runs the boot validators. */
487
+ readonly envSchema: ComposedEnvSchema;
445
488
  /** Prefix for `pulumi config set <prefix><CamelCase(VAR)>` in dry-run
446
489
  * output and boot-error suggestions. Without this, suggestions use
447
490
  * bare `camelCase(VAR)` and ops has to guess the app prefix. */
@@ -468,16 +511,18 @@ export type RunProdAppOptions = {
468
511
  * PrometheusMeter-basierten `observability`-Provider antwortet `/metrics`
469
512
  * mit 503 + Misconfiguration-Hinweis statt echter Metriken. */
470
513
  readonly metrics?: import("@cosmicdrift/kumiko-framework/api").ServerOptions["metrics"];
471
- /** L1 (Login-Lockout) + L2 (globaler IP-Limit) Rate-Limit-Middleware —
472
- * 1:1 durchgereicht an `buildServer`s `ServerOptions.rateLimit`. Ohne
473
- * dieses Feld wird kein L1/L2 verdrahtet (L3 Handler-`rateLimit:`
474
- * funktioniert unabhängig davon bereits). */
475
- readonly rateLimit?: import("@cosmicdrift/kumiko-framework/api").ServerOptions["rateLimit"];
514
+ /** L1 (global per-IP limit) + L2 (auth endpoints) rate-limit middleware, passed
515
+ * to `buildServer`'s `ServerOptions.rateLimit`. Without a value L1 allows 600/60 s
516
+ * and L2 30/60 s; a partial value overrides only the fields it names, `false`
517
+ * (whole option or one layer) switches it off. Behind a reverse proxy the
518
+ * client-IP key needs `trustedProxyHops`, otherwise all clients share one bucket. */
519
+ readonly rateLimit?: RateLimitOption;
476
520
  /** Default security headers on every response (HSTS, X-Frame-Options,
477
- * X-Content-Type-Options, Referrer-Policy; CSP opt-in). Headers already
478
- * set by a response (e.g. hostDispatch's per-host CSP) are never
479
- * overridden. `false` disables the whole block; per-header overrides
480
- * via the object form — see SecurityHeadersOption. */
521
+ * X-Content-Type-Options, Referrer-Policy, Content-Security-Policy with
522
+ * the build's inline-script hashes). Headers already set by a response
523
+ * (e.g. hostDispatch's per-host CSP) are never overridden. `false`
524
+ * disables the whole block; per-header overrides via the object form —
525
+ * see SecurityHeadersOption. */
481
526
  readonly securityHeaders?: SecurityHeadersOption;
482
527
  /** Number of trusted reverse-proxy hops for client-IP resolution — 1:1
483
528
  * durchgereicht an `buildServer`s