@cosmicdrift/kumiko-dev-server 0.359.0 → 0.361.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.
@@ -6,6 +6,7 @@ import { type TestStack, type TestStackOptions } from "@cosmicdrift/kumiko-frame
6
6
  import { type PageHeadResolver, type PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
7
7
  import { type SystemWireDeps } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
8
8
  type BunServer = typeof Bun extends undefined ? unknown : ReturnType<typeof Bun.serve>;
9
+ export declare const logInfo: (msg: string) => void;
9
10
  /** Multi-entry mode for apps that ship several separate bundles (e.g.
10
11
  * publicstatus: `admin.<base>` loads the admin UI, otherwise the public
11
12
  * page).
@@ -20,7 +20,7 @@ import { readFile, realpath, watch } from "node:fs/promises";
20
20
  import { tmpdir } from "node:os";
21
21
  import { join, resolve, sep } from "node:path";
22
22
  import { resolveAnonymousAccessFromRegistry } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
23
- import { createClientIpResolver, generateToken, } from "@cosmicdrift/kumiko-framework/api";
23
+ import { AUTH_COOKIE_BASE, CSRF_COOKIE_BASE, createClientIpResolver, generateToken, sessionCookieName, } from "@cosmicdrift/kumiko-framework/api";
24
24
  import { buildAppSchema } from "@cosmicdrift/kumiko-framework/engine";
25
25
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
26
26
  import { pushEntityProjectionTables, setupTestStack, TestUsers, } from "@cosmicdrift/kumiko-framework/stack";
@@ -32,6 +32,7 @@ import { RENDERER_WEB_FONT_FILE_PATTERN, RENDERER_WEB_FONTS_URL_PREFIX, resolveR
32
32
  import { buildRequestBoundSystemQuery } from "@cosmicdrift/kumiko-server-runtime/request-bound-system-query";
33
33
  import { canResolveTailwindStylesheet, resolveTailwindCli, } from "@cosmicdrift/kumiko-server-runtime/resolve-tailwind-cli";
34
34
  import { stripNoRouteMatchHeader, tryHonoFirst, } from "@cosmicdrift/kumiko-server-runtime/try-hono-first";
35
+ import { isPersistentDevDb } from "./is-persistent-dev-db.js";
35
36
  // Runtime-detection. The dev-server is meant to run under Bun (Kumiko's
36
37
  // target runtime), but the test-suite runs under vitest on Node — we
37
38
  // gate every Bun.* call so the module at least LOADS under Node, and
@@ -40,12 +41,13 @@ import { stripNoRouteMatchHeader, tryHonoFirst, } from "@cosmicdrift/kumiko-serv
40
41
  const hasBun = typeof globalThis.Bun !== "undefined";
41
42
  // @wrapper-known semantic-alias
42
43
  // biome-ignore lint/suspicious/noConsole: dev-server status logging
43
- const logInfo = (msg) => console.log(msg);
44
+ export const logInfo = (msg) => console.log(msg);
44
45
  // @wrapper-known semantic-alias
45
46
  // biome-ignore lint/suspicious/noConsole: dev-server error logging
46
47
  const logError = (...args) => console.error(...args);
47
- const CSRF_COOKIE = "kumiko_csrf";
48
- const AUTH_COOKIE = "kumiko_auth";
48
+ // The dev server speaks plain http, so its minted cookies are never Secure and keep the unprefixed names.
49
+ const CSRF_COOKIE = sessionCookieName(CSRF_COOKIE_BASE, { secure: false });
50
+ const AUTH_COOKIE = sessionCookieName(AUTH_COOKIE_BASE, { secure: false });
49
51
  // Duplicated in packages/testing/src/e2e/constants.ts: the Playwright config
50
52
  // runs under Node and cannot import this module.
51
53
  export const STYLESHEET_WATCH_ENV = "KUMIKO_DEV_STYLESHEET_WATCH";
@@ -588,7 +590,7 @@ export async function createKumikoServer(options) {
588
590
  // on purpose — every dev-server pattern benefits from the same
589
591
  // toggle, not just one sample.
590
592
  const devDbName = process.env["KUMIKO_DEV_DB_NAME"];
591
- const persistentDb = devDbName !== undefined && devDbName !== "";
593
+ const persistentDb = isPersistentDevDb(process.env);
592
594
  // Redis is shared across boots AND across apps (unlike the per-boot test
593
595
  // DB, which is ephemeral or persistent per KUMIKO_DEV_DB_NAME), so every
594
596
  // boot needs its own queue-name prefix — otherwise two dev/e2e servers
@@ -0,0 +1 @@
1
+ export declare function isPersistentDevDb(env?: Record<string, string | undefined>): boolean;
@@ -0,0 +1,4 @@
1
+ export function isPersistentDevDb(env = process.env) {
2
+ const devDbName = env["KUMIKO_DEV_DB_NAME"];
3
+ return devDbName !== undefined && devDbName !== "";
4
+ }
@@ -0,0 +1,13 @@
1
+ import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
2
+ export type DevPiiKmsResolution = {
3
+ readonly kms: KmsAdapter | undefined;
4
+ readonly blindIndexKey: string | undefined;
5
+ readonly mode: "app-kms" | "ephemeral-default" | "plaintext";
6
+ };
7
+ type DevPiiKmsOptions = {
8
+ readonly kms?: KmsAdapter | false;
9
+ readonly blindIndexKey?: string;
10
+ };
11
+ export declare function captureEphemeralDevPiiKms(resolution: DevPiiKmsResolution): () => void;
12
+ export declare function resolveDevPiiKms(options: DevPiiKmsOptions, env?: Record<string, string | undefined>): DevPiiKmsResolution;
13
+ export {};
@@ -0,0 +1,33 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { configureBlindIndexKey, configuredBlindIndexKey, configuredPiiSubjectKms, configurePiiSubjectKms, InMemoryKmsAdapter, } from "@cosmicdrift/kumiko-framework/crypto";
3
+ import { isPersistentDevDb } from "./is-persistent-dev-db.js";
4
+ // Call right after configuring the resolved values. The returned restore only
5
+ // unsets what the ephemeral default installed, so a KMS a later test or the app
6
+ // configured survives.
7
+ export function captureEphemeralDevPiiKms(resolution) {
8
+ if (resolution.mode !== "ephemeral-default")
9
+ return () => { };
10
+ const ownKms = configuredPiiSubjectKms();
11
+ const ownBlindIndexKey = configuredBlindIndexKey();
12
+ return () => {
13
+ if (ownKms !== undefined && configuredPiiSubjectKms() === ownKms) {
14
+ configurePiiSubjectKms(undefined);
15
+ }
16
+ if (ownBlindIndexKey !== undefined && configuredBlindIndexKey() === ownBlindIndexKey) {
17
+ configureBlindIndexKey(undefined);
18
+ }
19
+ };
20
+ }
21
+ export function resolveDevPiiKms(options, env = process.env) {
22
+ if (options.kms !== undefined && options.kms !== false) {
23
+ return { kms: options.kms, blindIndexKey: options.blindIndexKey, mode: "app-kms" };
24
+ }
25
+ if (options.kms === false || isPersistentDevDb(env)) {
26
+ return { kms: undefined, blindIndexKey: options.blindIndexKey, mode: "plaintext" };
27
+ }
28
+ return {
29
+ kms: new InMemoryKmsAdapter(),
30
+ blindIndexKey: options.blindIndexKey ?? randomBytes(32).toString("base64"),
31
+ mode: "ephemeral-default",
32
+ };
33
+ }
@@ -120,12 +120,12 @@ export type RunDevAppOptions = {
120
120
  * `createEnvMasterKeyProvider`. Override für KMS-Backends. Nur relevant
121
121
  * wenn das secrets-Feature gemountet ist. Symmetrisch zu runProdApp. */
122
122
  readonly masterKey?: MasterKeyProvider;
123
- /** Subject-Key-KMS für pii-annotierte Felder (Crypto-Shredding) — dev-
124
- * Pendant zu runProdApp({ kms }). Ephemere DB: InMemoryKmsAdapter reicht.
125
- * Persistente Dev-DB: createPgKmsAdapter gegen dieselbe DB, sonst sind
126
- * alte Rows nach dem Restart unlesbar (DEKs weg). Ohne Adapter: Klartext
127
- * + Boot-Warnung. */
128
- readonly kms?: KmsAdapter;
123
+ /** Subject KMS for PII-annotated fields, the dev counterpart of runProdApp({ kms }).
124
+ * Unset: an ephemeral DB gets an InMemoryKmsAdapter plus a throwaway blind-index key;
125
+ * a persistent dev DB (KUMIKO_DEV_DB_NAME) stays plaintext with a boot warning, pass
126
+ * createPgKmsAdapter against that DB there (in-memory DEKs vanish on restart).
127
+ * `false`: explicit plaintext, with the boot warning. */
128
+ readonly kms?: KmsAdapter | false;
129
129
  /** 32-Byte-Key (base64) für Blind-Index-HMACs — Pflicht sobald `kms`
130
130
  * gesetzt ist und lookupable-Felder gemountet sind (sonst Boot-Abbruch,
131
131
  * symmetrisch zu runProdApp). */
@@ -30,7 +30,8 @@ import { buildComposeAuthOptions, composeFeatures, } from "@cosmicdrift/kumiko-s
30
30
  import { assertPiiBootInvariants } from "@cosmicdrift/kumiko-server-runtime/pii-boot-gate";
31
31
  import { assertSessionBootInvariants } from "@cosmicdrift/kumiko-server-runtime/session-boot-gate";
32
32
  import { watchAndRegenerate } from "./codegen/index.js";
33
- import { createKumikoServer, } from "./create-kumiko-server.js";
33
+ import { createKumikoServer, logInfo, } from "./create-kumiko-server.js";
34
+ import { captureEphemeralDevPiiKms, resolveDevPiiKms } from "./resolve-dev-pii-kms.js";
34
35
  import { renderWelcomeBanner } from "./welcome-banner.js";
35
36
  import { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, requireEnv, resolveAuthMail, } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
36
37
  export async function runDevApp(options) {
@@ -128,13 +129,18 @@ export async function runDevApp(options) {
128
129
  // Subject-KMS + Blind-Index (symmetrisch zu runProdApp). Dev warnt bei
129
130
  // Klartext-PII statt zu failen; kms+lookupable ohne blindIndexKey bricht
130
131
  // auch hier (Lookups wären in jedem Modus kaputt).
131
- configurePiiSubjectKms(options.kms);
132
- configureBlindIndexKey(options.blindIndexKey);
132
+ const piiKms = resolveDevPiiKms(options);
133
+ configurePiiSubjectKms(piiKms.kms);
134
+ configureBlindIndexKey(piiKms.blindIndexKey);
135
+ const releaseEphemeralPiiKms = captureEphemeralDevPiiKms(piiKms);
133
136
  assertPiiBootInvariants(features, {
134
- kms: options.kms,
135
- blindIndexKey: options.blindIndexKey,
137
+ kms: piiKms.kms,
138
+ blindIndexKey: piiKms.blindIndexKey,
136
139
  mode: "dev",
137
140
  });
141
+ if (piiKms.mode === "ephemeral-default") {
142
+ logInfo("[runDevApp] PII encrypted with in-memory KMS (ephemeral DB); pass kms: false to opt out");
143
+ }
138
144
  // Throwaway registry, built once and reused below — mirrors runProdApp's
139
145
  // `createRegistry(features)` right after validateBoot (#2027). Symmetric
140
146
  // sessionStoreProviderMounted check to runProdApp's (registry.getExtension-
@@ -364,6 +370,9 @@ export async function runDevApp(options) {
364
370
  await seed(stack);
365
371
  }
366
372
  },
373
+ }).catch((error) => {
374
+ releaseEphemeralPiiKms();
375
+ throw error;
367
376
  });
368
377
  if (options.welcomeBanner) {
369
378
  const overrides = typeof options.welcomeBanner === "object" ? options.welcomeBanner : {};
@@ -382,7 +391,17 @@ export async function runDevApp(options) {
382
391
  // biome-ignore lint/suspicious/noConsole: boot-time UX print, opt-in via welcomeBanner.
383
392
  console.log(`\n${banner}\n`);
384
393
  }
385
- return handle;
394
+ return {
395
+ ...handle,
396
+ stop: async () => {
397
+ try {
398
+ await handle.stop();
399
+ }
400
+ finally {
401
+ releaseEphemeralPiiKms();
402
+ }
403
+ },
404
+ };
386
405
  }
387
406
  // Exported for the wiring-contract test (config-resolver-default.integration):
388
407
  // pins that the dev configResolver-default carries the ENV→app-override bridge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.359.0",
3
+ "version": "0.361.0",
4
4
  "description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -64,11 +64,11 @@
64
64
  "kumiko-upgrade": "./bin/kumiko-upgrade.ts"
65
65
  },
66
66
  "dependencies": {
67
- "@cosmicdrift/kumiko-bundled-features": "0.359.0",
68
- "@cosmicdrift/kumiko-framework": "0.359.0",
69
- "@cosmicdrift/kumiko-headless": "0.359.0",
70
- "@cosmicdrift/kumiko-renderer-web": "0.359.0",
71
- "@cosmicdrift/kumiko-server-runtime": "0.359.0",
67
+ "@cosmicdrift/kumiko-bundled-features": "0.361.0",
68
+ "@cosmicdrift/kumiko-framework": "0.361.0",
69
+ "@cosmicdrift/kumiko-headless": "0.361.0",
70
+ "@cosmicdrift/kumiko-renderer-web": "0.361.0",
71
+ "@cosmicdrift/kumiko-server-runtime": "0.361.0",
72
72
  "bun-types": "^1.3.13",
73
73
  "ts-morph": "^28.0.0",
74
74
  "zod": "^4.4.3"
package/src/changes.json CHANGED
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.361.0",
4
+ "type": "breaking",
5
+ "title": "runDevApp encrypts PII on ephemeral dev DBs by default",
6
+ "detail": "Without a kms option, runDevApp now configures an InMemoryKmsAdapter and a random blind-index key when KUMIKO_DEV_DB_NAME is unset. With a persistent dev DB nothing changes (plaintext plus warning, pass createPgKmsAdapter to encrypt). RunDevAppOptions.kms also accepts false for explicit plaintext. An app-provided kms keeps its previous behavior, including the boot abort for lookupable fields without a blindIndexKey.",
7
+ "migration": "e2e or dev scripts that read PII columns raw must decrypt them or pass kms false to runDevApp. Apps that pass their own kms are unaffected."
8
+ },
2
9
  {
3
10
  "version": "0.354.0",
4
11
  "type": "fix",