@cosmicdrift/kumiko-server-runtime 0.369.0 → 0.371.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/dist/boot/boot-crypto.js +17 -11
- package/dist/boot/worker-boot-core.d.ts +1 -1
- package/dist/boot-options/billing-option.d.ts +37 -0
- package/dist/boot-options/billing-option.js +36 -0
- package/dist/boot-options/demo-mode.d.ts +51 -0
- package/dist/boot-options/demo-mode.js +64 -0
- package/dist/boot-options/file-storage-option.d.ts +30 -0
- package/dist/boot-options/file-storage-option.js +52 -0
- package/dist/boot-options/kms-mode.d.ts +27 -0
- package/dist/boot-options/kms-mode.js +40 -0
- package/dist/boot-options/mail-option.d.ts +31 -0
- package/dist/boot-options/mail-option.js +43 -0
- package/dist/boot-options/option-env-schema.d.ts +7 -0
- package/dist/boot-options/option-env-schema.js +113 -0
- package/dist/boot-options/process-signals.d.ts +23 -0
- package/dist/boot-options/process-signals.js +58 -0
- package/dist/boot-options/resolve-boot-options.d.ts +44 -0
- package/dist/boot-options/resolve-boot-options.js +88 -0
- package/dist/boot-options/seed-shortcuts.d.ts +49 -0
- package/dist/boot-options/seed-shortcuts.js +92 -0
- package/dist/boot-options/stack-boot-requirements.d.ts +6 -0
- package/dist/boot-options/stack-boot-requirements.js +63 -0
- package/dist/build-prod-bundle.d.ts +4 -0
- package/dist/build-prod-bundle.js +41 -2
- package/dist/csp-hashes.d.ts +15 -0
- package/dist/csp-hashes.js +46 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +4 -1
- package/dist/rate-limit-defaults.d.ts +19 -0
- package/dist/rate-limit-defaults.js +13 -0
- package/dist/run-bootstrap.d.ts +2 -2
- package/dist/run-prod-app-boot-context.d.ts +5 -3
- package/dist/run-prod-app-boot-context.js +4 -2
- package/dist/run-prod-app-static-files.d.ts +6 -2
- package/dist/run-prod-app-static-files.js +22 -1
- package/dist/run-prod-app.d.ts +81 -29
- package/dist/run-prod-app.js +164 -98
- package/dist/run-worker-app.d.ts +2 -2
- package/dist/security-headers.d.ts +16 -5
- package/dist/security-headers.js +56 -5
- package/package.json +18 -9
- package/src/changes.json +55 -0
|
@@ -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.
|
|
12
|
-
import type { AuthMailOptions, EmailVerificationSetup, InviteSetup, PasswordResetSetup, SignupSetup } from "./run-prod-app.
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
-
import type { HostDispatchFn, PageHeadResolver } from "./run-prod-app.
|
|
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.
|
package/dist/run-prod-app.d.ts
CHANGED
|
@@ -6,21 +6,32 @@ import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
|
6
6
|
import { type EffectiveFeaturesResolver, type FeatureDefinition, type ValidateBootOptions } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { type AllInOneEntrypoint, type ApiEntrypoint } from "@cosmicdrift/kumiko-framework/entrypoint";
|
|
8
8
|
import { type ComposedEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
|
|
9
|
+
import { type DryRunEnvMode } from "@cosmicdrift/kumiko-framework/env/dry-run";
|
|
9
10
|
import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
|
|
10
11
|
import type { JobRunnerOptions } from "@cosmicdrift/kumiko-framework/jobs";
|
|
11
12
|
import { type ObservabilityOptions, type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
|
|
12
13
|
import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
13
14
|
import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
|
|
14
15
|
import { Redis } from "ioredis";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
import type { BillingBootOption } from "./boot-options/billing-option.ts";
|
|
17
|
+
import type { DemoModeOption } from "./boot-options/demo-mode.ts";
|
|
18
|
+
import type { FileStorageBootOption } from "./boot-options/file-storage-option.ts";
|
|
19
|
+
import type { KmsMode } from "./boot-options/kms-mode.ts";
|
|
20
|
+
import type { MailBootOption } from "./boot-options/mail-option.ts";
|
|
21
|
+
import { type SignalsOption } from "./boot-options/process-signals.ts";
|
|
22
|
+
import type { ProdSeedsConfig } from "./boot-options/seed-shortcuts.ts";
|
|
23
|
+
import { type SystemWireDeps } from "./extra-routes-deps.ts";
|
|
24
|
+
import { type RateLimitOption } from "./rate-limit-defaults.ts";
|
|
25
|
+
import { type UnsubscribeOption } from "./resolve-unsubscribe-wiring.ts";
|
|
26
|
+
import { type SecurityHeadersOption } from "./security-headers.ts";
|
|
27
|
+
import { type SeedCreateFileRef } from "./seed-dispatcher.ts";
|
|
28
|
+
export { buildBunServeOptions } from "./bun-serve-options.ts";
|
|
29
|
+
export { resolveUnsubscribeWiring, type UnsubscribeOption, } from "./resolve-unsubscribe-wiring.ts";
|
|
30
|
+
export { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.ts";
|
|
31
|
+
export { staticCachePolicy } from "./run-prod-app-static-files.ts";
|
|
23
32
|
export declare function requireEnv(name: string, src?: Record<string, string | undefined>, context?: string): string;
|
|
33
|
+
type RunMode = DryRunEnvMode;
|
|
34
|
+
export declare function parseRunMode(raw: string | undefined): RunMode | null;
|
|
24
35
|
/** Wrapper API for the password-reset flow.
|
|
25
36
|
*
|
|
26
37
|
* Since the delivery migration PasswordResetOptions carries `appUrl`
|
|
@@ -193,6 +204,8 @@ export type RunProdAppDeps = {
|
|
|
193
204
|
readonly redis: Redis;
|
|
194
205
|
readonly registry: import("@cosmicdrift/kumiko-framework/engine").Registry;
|
|
195
206
|
readonly sseBroker: SseBroker;
|
|
207
|
+
/** The env the boot runs on: process env or `envSource`, with the boot-option defaults (e.g. `fileStorage`) underneath. */
|
|
208
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
196
209
|
};
|
|
197
210
|
export type AnonymousAccessOption = import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig | ((deps: RunProdAppDeps) => import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig);
|
|
198
211
|
export type ExtraContextOption = Record<string, unknown> | ((deps: RunProdAppDeps) => Record<string, unknown>);
|
|
@@ -258,8 +271,13 @@ export type RunProdAppOptions = {
|
|
|
258
271
|
readonly maxRequestBodySize?: number;
|
|
259
272
|
/** Auth-mode: standard features + routes wired, admin seeded. */
|
|
260
273
|
readonly auth?: RunProdAppAuthOptions;
|
|
261
|
-
/** Custom seed functions, run after the admin seed (when auth-mode).
|
|
262
|
-
|
|
274
|
+
/** Custom seed functions, run after the admin seed (when auth-mode). The
|
|
275
|
+
* object form adds the declarative demo-tenant / demo-admin / sysadmin
|
|
276
|
+
* seeds from the env and takes the functions under `run`; see
|
|
277
|
+
* `ProdSeedsConfig`.
|
|
278
|
+
* @example
|
|
279
|
+
* seeds: { demoTenant: { key: "demo", name: "Demo" }, sysadminFromEnv: true, run: [seedDemoData] } */
|
|
280
|
+
readonly seeds?: readonly ProdSeedFn[] | ProdSeedsConfig;
|
|
263
281
|
/** Pfad zum seeds-Directory für ES-Operations / Seed-Migrations
|
|
264
282
|
* (file-basiert wie drizzle-migrate). Wenn gesetzt + KUMIKO_SKIP_ES_OPS
|
|
265
283
|
* != "1": runProdApp scannt das Verzeichnis nach `<id>.ts` Files,
|
|
@@ -354,6 +372,39 @@ export type RunProdAppOptions = {
|
|
|
354
372
|
* ist eine Begründung für den Audit-Trail (z.B. "kms rollout pending,
|
|
355
373
|
* infra#188"). Ohne Flag und ohne `kms` bricht der Boot ab. */
|
|
356
374
|
readonly allowPlaintextPii?: string;
|
|
375
|
+
/** Short form over `kms` / `blindIndexKey` / `allowPlaintextPii` that wires
|
|
376
|
+
* the subject-key KMS from the env; not combinable with them. Unset keeps
|
|
377
|
+
* today's behavior: no KMS unless `kms` is passed, and the PII boot gate
|
|
378
|
+
* aborts when PII fields exist without one. See `KmsMode`.
|
|
379
|
+
* @example
|
|
380
|
+
* kmsMode: "required" */
|
|
381
|
+
readonly kmsMode?: KmsMode;
|
|
382
|
+
/** App-wide mail transport: mounts `channel-email` and wires the `auth.mail`
|
|
383
|
+
* flows without an `SMTP_HOST` probe. See `MailBootOption`.
|
|
384
|
+
* @example
|
|
385
|
+
* mail: { transport: "smtp-env", from: "Acme <noreply@acme.example>" } */
|
|
386
|
+
readonly mail?: MailBootOption;
|
|
387
|
+
/** File provider for `file-foundation` plus the `FILE_STORAGE_PROVIDER`
|
|
388
|
+
* default. See `FileStorageBootOption`.
|
|
389
|
+
* @example
|
|
390
|
+
* fileStorage: { provider: "auto" } */
|
|
391
|
+
readonly fileStorage?: FileStorageBootOption;
|
|
392
|
+
/** Stripe billing: `subscription-stripe` plus the tier-syncing webhook
|
|
393
|
+
* route. Needs `billing-foundation` and `tier-engine`. See `BillingBootOption`.
|
|
394
|
+
* @example
|
|
395
|
+
* billing: { stripe: { priceToTier: { [env.STRIPE_PRICE_PRO]: "pro" } }, defaultTier: "free" } */
|
|
396
|
+
readonly billing?: BillingBootOption;
|
|
397
|
+
/** Live-demo mode: read-only gate in the fetch chain plus the anonymous
|
|
398
|
+
* `GET /demo-mode` route. See `DemoModeOption`.
|
|
399
|
+
* @example
|
|
400
|
+
* demoMode: { readOnly: env.DEMO_READ_ONLY === "true", notice: { text: { en: "Read-only demo" } } } */
|
|
401
|
+
readonly demoMode?: DemoModeOption;
|
|
402
|
+
/** SIGTERM/SIGINT handling of `listen()`: drains via `stop()` (default
|
|
403
|
+
* timeout 25 s), exits 0, or 1 when the drain fails or times out. `false`
|
|
404
|
+
* leaves the signals to the app.
|
|
405
|
+
* @example
|
|
406
|
+
* signals: { drainTimeoutMs: 10_000 } */
|
|
407
|
+
readonly signals?: SignalsOption;
|
|
357
408
|
/** Deploy topology. Default `true` (single container): this process runs
|
|
358
409
|
* HTTP, both job lanes (api + worker) and the event dispatcher (MSP apply)
|
|
359
410
|
* inline via `createAllInOneEntrypoint`. Worker-lane crons (e.g. the data
|
|
@@ -423,23 +474,22 @@ export type RunProdAppOptions = {
|
|
|
423
474
|
* the fetch-handler directly (Bun.serve isn't available under vitest +
|
|
424
475
|
* node), then call handle.listen() manually if needed. */
|
|
425
476
|
readonly autoListen?: boolean;
|
|
426
|
-
/**
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* seed-fn (db ist erst nach migrations + features ready). */
|
|
477
|
+
/** Per-tenant feature resolver, passed to the dispatcher.
|
|
478
|
+
* @deprecated Removed in the next release. `createTierEngineFeature({ tierMap })`
|
|
479
|
+
* (or `composeSaasStack`) builds the resolver at boot, and prod-only features
|
|
480
|
+
* come from the `mail`, `fileStorage` and `billing` options. */
|
|
431
481
|
readonly effectiveFeatures?: EffectiveFeaturesResolver;
|
|
432
482
|
/** Composed Zod-schema for env-validation (from `composeEnvSchema({
|
|
433
|
-
* features, extend })` in @cosmicdrift/kumiko-framework/env)
|
|
483
|
+
* core, features, extend })` in @cosmicdrift/kumiko-framework/env):
|
|
434
484
|
* - `process.env` is parsed against it BEFORE any boot work; missing
|
|
435
485
|
* or invalid vars throw a `KumikoBootError` listing ALL problems
|
|
436
486
|
* at once (not first-fail).
|
|
437
487
|
* - `KUMIKO_DRY_RUN_ENV=human|json|pulumi|k8s` introspects the schema
|
|
438
488
|
* and prints the env-var inventory, then exits without booting.
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
readonly envSchema
|
|
489
|
+
* - `KUMIKO_DRY_RUN_ENV=boot` prints a per-variable table. With required
|
|
490
|
+
* values missing it exits 0 without validating the feature wiring; an
|
|
491
|
+
* invalid value exits 1; a complete env runs the boot validators. */
|
|
492
|
+
readonly envSchema: ComposedEnvSchema;
|
|
443
493
|
/** Prefix for `pulumi config set <prefix><CamelCase(VAR)>` in dry-run
|
|
444
494
|
* output and boot-error suggestions. Without this, suggestions use
|
|
445
495
|
* bare `camelCase(VAR)` and ops has to guess the app prefix. */
|
|
@@ -466,16 +516,18 @@ export type RunProdAppOptions = {
|
|
|
466
516
|
* PrometheusMeter-basierten `observability`-Provider antwortet `/metrics`
|
|
467
517
|
* mit 503 + Misconfiguration-Hinweis statt echter Metriken. */
|
|
468
518
|
readonly metrics?: import("@cosmicdrift/kumiko-framework/api").ServerOptions["metrics"];
|
|
469
|
-
/** L1 (
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
|
|
519
|
+
/** L1 (global per-IP limit) + L2 (auth endpoints) rate-limit middleware, passed
|
|
520
|
+
* to `buildServer`'s `ServerOptions.rateLimit`. Without a value L1 allows 600/60 s
|
|
521
|
+
* and L2 30/60 s; a partial value overrides only the fields it names, `false`
|
|
522
|
+
* (whole option or one layer) switches it off. Behind a reverse proxy the
|
|
523
|
+
* client-IP key needs `trustedProxyHops`, otherwise all clients share one bucket. */
|
|
524
|
+
readonly rateLimit?: RateLimitOption;
|
|
474
525
|
/** Default security headers on every response (HSTS, X-Frame-Options,
|
|
475
|
-
* X-Content-Type-Options, Referrer-Policy
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
* via the object form —
|
|
526
|
+
* X-Content-Type-Options, Referrer-Policy, Content-Security-Policy with
|
|
527
|
+
* the build's inline-script hashes). Headers already set by a response
|
|
528
|
+
* (e.g. hostDispatch's per-host CSP) are never overridden. `false`
|
|
529
|
+
* disables the whole block; per-header overrides via the object form —
|
|
530
|
+
* see SecurityHeadersOption. */
|
|
479
531
|
readonly securityHeaders?: SecurityHeadersOption;
|
|
480
532
|
/** Number of trusted reverse-proxy hops for client-IP resolution — 1:1
|
|
481
533
|
* durchgereicht an `buildServer`s
|