@cosmicdrift/kumiko-server-runtime 0.320.0 → 0.322.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-server-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.322.0",
|
|
4
4
|
"description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold/codegen tooling.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
80
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
81
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
79
|
+
"@cosmicdrift/kumiko-bundled-features": "0.322.0",
|
|
80
|
+
"@cosmicdrift/kumiko-framework": "0.322.0",
|
|
81
|
+
"@cosmicdrift/kumiko-headless": "0.322.0",
|
|
82
82
|
"temporal-polyfill": "^0.3.2"
|
|
83
83
|
},
|
|
84
84
|
"publishConfig": {
|
|
@@ -359,6 +359,27 @@ describe("runProdApp", () => {
|
|
|
359
359
|
expect(body).toContain('<probe ok="true" />');
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
test("handle.fetch threads the socket address into deps.clientIp, ignoring a spoofed X-Forwarded-For at the default trustedProxyHops (0)", async () => {
|
|
363
|
+
const handle = await boot(undefined, {
|
|
364
|
+
extraRoutes: [
|
|
365
|
+
{
|
|
366
|
+
method: "GET",
|
|
367
|
+
path: "/client-ip-probe",
|
|
368
|
+
entry: "anonymous",
|
|
369
|
+
handler: (c, deps) => c.body(deps.clientIp, 200),
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
});
|
|
373
|
+
const req = () =>
|
|
374
|
+
new Request("http://test/client-ip-probe", { headers: { "x-forwarded-for": "1.2.3.4" } });
|
|
375
|
+
|
|
376
|
+
const withSocket = await handle.fetch(req(), "198.51.100.7");
|
|
377
|
+
expect(await withSocket.text()).toBe("198.51.100.7");
|
|
378
|
+
|
|
379
|
+
const withoutSocket = await handle.fetch(req());
|
|
380
|
+
expect(await withoutSocket.text()).toBe("unknown");
|
|
381
|
+
});
|
|
382
|
+
|
|
362
383
|
test("extraRoutes: entry:signature dispatchSystemWrite schreibt als SystemAdmin des Ziel-Tenants, registry verfügbar", async () => {
|
|
363
384
|
// Wiring for provider webhook routes (billing-foundation
|
|
364
385
|
// createSubscriptionWebhookRoute): the route authenticates via
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.321.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "runProdApp's KUMIKO_TRUSTED_PROXY_HOPS parsing now shares framework's parseTrustedProxyHopsEnv",
|
|
6
|
+
"detail": "The inline digits-only + non-negative-integer validation moved out of\n`run-prod-app.ts` into `parseTrustedProxyHopsEnv`\n(`@cosmicdrift/kumiko-framework/api`); same validation and error message\nas before, no behavioral change.",
|
|
7
|
+
"migration": "No action needed."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.321.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "ProdAppHandle.fetch's type now declares its optional socket-address parameter",
|
|
13
|
+
"detail": "`fetch: (req: Request, socketAddress?: string) => Promise<Response> |\nResponse`. The doc comment now explains that an app with `autoListen:\nfalse` running its own `Bun.serve` must pass\n`server.requestIP(req)?.address`, extracted at the outermost Bun fetch\ncallback — `requestIP()` only resolves for Bun's original Request\ninstance, not a cloned/rebuilt one. `RunProdAppAuthOptions\n.trustedProxyHops`'s doc comment also dropped the stale \"pre-#1539\nspoofable default\" wording; 0 hops is the safe socket-only default.",
|
|
14
|
+
"migration": "No action needed for the default `autoListen: true` boot. Apps with\n`autoListen: false` can now type-check passing the socket address to\n`handle.fetch`; passing nothing keeps the previous (unresolved) behavior."
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "0.320.0",
|
|
4
18
|
"type": "breaking",
|
package/src/run-prod-app.ts
CHANGED
|
@@ -76,8 +76,10 @@ import {
|
|
|
76
76
|
type LoginRateLimiter,
|
|
77
77
|
loadJwtSecretOrKeyring,
|
|
78
78
|
type PostAuthLandingResolver,
|
|
79
|
+
parseTrustedProxyHopsEnv,
|
|
79
80
|
type SseBroker,
|
|
80
81
|
type TokenVerifier,
|
|
82
|
+
TRUSTED_PROXY_HOPS_ENV,
|
|
81
83
|
} from "@cosmicdrift/kumiko-framework/api";
|
|
82
84
|
import {
|
|
83
85
|
configureBlindIndexKey,
|
|
@@ -380,8 +382,10 @@ export type RunProdAppAuthOptions = {
|
|
|
380
382
|
* process for client-IP derivation (see AuthRoutesConfig.trustedProxyHops,
|
|
381
383
|
* kumiko-framework#1539) — closes the X-Forwarded-For spoofing hole on
|
|
382
384
|
* the auth rate-limiters. Falls back to the `KUMIKO_TRUSTED_PROXY_HOPS`
|
|
383
|
-
* env var when unset; both unset means the
|
|
384
|
-
* (
|
|
385
|
+
* env var when unset; both unset means 0 hops, the safe socket-only
|
|
386
|
+
* default (only the socket address counts, XFF ignored). Deprecated in
|
|
387
|
+
* favor of the top-level `trustedProxyHops` option, which wins over
|
|
388
|
+
* this and the env var. */
|
|
385
389
|
readonly trustedProxyHops?: number;
|
|
386
390
|
/** Server-computed post-auth redirect (see AuthRoutesConfig.postAuthLanding).
|
|
387
391
|
* Result lands in the login/signup/invite response as `landingPath`; an
|
|
@@ -700,8 +704,12 @@ export type ProdAppHandle = {
|
|
|
700
704
|
* `process.exit(0)` before returning a handle. */
|
|
701
705
|
readonly entrypoint: ApiEntrypoint | AllInOneEntrypoint;
|
|
702
706
|
/** The fetch-handler — wired into Bun.serve in production, called
|
|
703
|
-
* directly in tests. Composes Hono + static-fallback.
|
|
704
|
-
|
|
707
|
+
* directly in tests. Composes Hono + static-fallback. Apps with
|
|
708
|
+
* `autoListen: false` that call `Bun.serve` themselves must pass the
|
|
709
|
+
* 2nd arg from `server.requestIP(req)?.address`, extracted in their
|
|
710
|
+
* outermost Bun fetch callback — `requestIP` only resolves for Bun's
|
|
711
|
+
* original Request instance, never a cloned/rebuilt one. */
|
|
712
|
+
readonly fetch: (req: Request, socketAddress?: string) => Promise<Response> | Response;
|
|
705
713
|
/** Active Bun-server (only set when listen() was called — tests skip
|
|
706
714
|
* listen() because Bun.serve isn't available under vitest/node). */
|
|
707
715
|
server?: ReturnType<typeof Bun.serve>;
|
|
@@ -818,31 +826,10 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
818
826
|
// kumiko-framework#1539 — options.auth.trustedProxyHops wins; falls back
|
|
819
827
|
// to the env var so ops can close the XFF-spoofing hole per-deployment
|
|
820
828
|
// without a code change (mirrors instanceId's env-first pattern above).
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
// deployment — a self-inflicted DoS, worse than staying on the default.
|
|
826
|
-
const trustedProxyHopsFromEnv = readEnv("KUMIKO_TRUSTED_PROXY_HOPS", envSource);
|
|
827
|
-
const trustedProxyHops = ((): number | undefined => {
|
|
828
|
-
if (options.trustedProxyHops !== undefined) return options.trustedProxyHops;
|
|
829
|
-
if (options.auth?.trustedProxyHops !== undefined) return options.auth.trustedProxyHops;
|
|
830
|
-
if (trustedProxyHopsFromEnv === undefined) return undefined;
|
|
831
|
-
// Digits-only — parseInt("0x10")/("1e3")/("2x") would silently coerce
|
|
832
|
-
// and land on the spoofable hops=0 path for values like "0x10".
|
|
833
|
-
if (!/^\d+$/.test(trustedProxyHopsFromEnv)) {
|
|
834
|
-
throw new Error(
|
|
835
|
-
`runProdApp: KUMIKO_TRUSTED_PROXY_HOPS must be a non-negative integer, got "${trustedProxyHopsFromEnv}".`,
|
|
836
|
-
);
|
|
837
|
-
}
|
|
838
|
-
const parsed = Number.parseInt(trustedProxyHopsFromEnv, 10);
|
|
839
|
-
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
840
|
-
throw new Error(
|
|
841
|
-
`runProdApp: KUMIKO_TRUSTED_PROXY_HOPS must be a non-negative integer, got "${trustedProxyHopsFromEnv}".`,
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
return parsed;
|
|
845
|
-
})();
|
|
829
|
+
const trustedProxyHops =
|
|
830
|
+
options.trustedProxyHops ??
|
|
831
|
+
options.auth?.trustedProxyHops ??
|
|
832
|
+
parseTrustedProxyHopsEnv(readEnv(TRUSTED_PROXY_HOPS_ENV, envSource), "runProdApp");
|
|
846
833
|
const port = options.port ?? Number.parseInt(envSource["PORT"] ?? "3000", 10);
|
|
847
834
|
|
|
848
835
|
// biome-ignore lint/suspicious/noConsole: boot-time progress hint, no logger configured this early
|