From 217a36252ef2cc972364f7cc43cded31d8251aa3 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 13:48:43 +0100 Subject: [PATCH 1/6] Keep a URL config value out of the boot error that rejects it --- CHANGELOG.md | 6 ++++++ docs/reading-cost.md | 13 +++++++++++++ src/auth/bootstrap.test.ts | 10 ++++++++++ src/auth/bootstrap.ts | 5 +++-- src/config.test.ts | 23 +++++++++++++++++++++++ src/config.ts | 22 ++++++++++++---------- todo.md | 15 --------------- 7 files changed, 67 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2a9d9..74d3b46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ at boot. Entries start at 0.3.0. ## Unreleased +### A malformed URL setting's boot error no longer prints the value + +When a URL setting such as `PLUGIN_DB_URL`, `PLUGIN_DB_ADMIN_URL` or `OTLP_ENDPOINT` can't be parsed, +the boot error names the variable and not its value, so a DSN whose password breaks parsing doesn't +put the password in the log. + ### A secret plugin setting's bad value stays out of the boot error When a `secret` setting's value fails validation, the boot error no longer prints it, so a malformed diff --git a/docs/reading-cost.md b/docs/reading-cost.md index c3d477d..2e1aaa0 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -291,3 +291,16 @@ Read most (opens and searches): - `e2e-tests/full-flow.spec.ts` 1 - `examples/plugins/admin/admin-users.test.ts` 1 - `examples/plugins/admin/admin-users.ts` 1 + +## Reading cost, chunk f0a49c300aac, finished 2026-10-31T12:47:27Z + +63526 new input tokens before the first commit. + +Read most (opens and searches): + +- `README.md` 4 +- `src/plugin-host/settings.test.ts` 3 +- `todo.md` 3 +- `CHANGELOG.md` 2 +- `src/plugin-host/settings.ts` 2 +- `AGENTS.md` 1 diff --git a/src/auth/bootstrap.test.ts b/src/auth/bootstrap.test.ts index f57655b..5040804 100644 --- a/src/auth/bootstrap.test.ts +++ b/src/auth/bootstrap.test.ts @@ -181,6 +181,16 @@ test("provisioning still runs with nothing to provision, so orphans are reported assert.deepEqual(calls[0]?.pluginIds, []); }); +// The postgres driver rethrows a malformed DSN with the whole string on the error (`input`), so +// the URL is checked here, before the driver sees it. +test("a malformed PLUGIN_DB_ADMIN_URL is refused by name, never echoing the password", async () => { + const env = { PLUGIN_DB_ADMIN_URL: "postgres://admin:pa/ss@db:5432/plugins" }; + await assert.rejects( + provisionPluginStorage(env, [storagePlugin("things")], SILENT, async () => ({ orphans: [], provisioned: [] })), + (err: Error) => err.message.includes("PLUGIN_DB_ADMIN_URL") && !err.message.includes("pa/ss"), + ); +}); + test("a plugin declaring storage without a provisioning DSN fails loud, naming the plugin", async () => { const { calls, provision } = recordingProvisioner(); await assert.rejects( diff --git a/src/auth/bootstrap.ts b/src/auth/bootstrap.ts index 137ef59..2e3c732 100644 --- a/src/auth/bootstrap.ts +++ b/src/auth/bootstrap.ts @@ -8,7 +8,7 @@ // Then prints a first-run banner; fails loud on any unexpected upstream error. import { existsSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; -import { resolvePluginDbConnectionLimit, resolvePluginDbSecret } from "../config.ts"; +import { assertUrl, resolvePluginDbConnectionLimit, resolvePluginDbSecret } from "../config.ts"; import { discoverPlugins } from "../plugin-host/discovery.ts"; import { declaredPermissions, isValidPermissionName, type Plugin } from "../plugin-host/plugin.ts"; import { provisionStorage } from "../plugin-host/storage-provisioning.ts"; @@ -179,6 +179,7 @@ export async function provisionPluginStorage(env: Env, plugins: Plugin[], log: L // Provisioned here, connected to from web: a different server means the role is created in one // place and looked for in another, surfacing inside a plugin as "password authentication failed". // Warned, not refused — web reaching a pooler that cannot run CREATE DATABASE is a legitimate split. + assertUrl("PLUGIN_DB_ADMIN_URL", adminUrl); const mismatch = serverMismatch(adminUrl, env["PLUGIN_DB_URL"]); if (mismatch) log.warn("PLUGIN_DB_ADMIN_URL and PLUGIN_DB_URL name different servers", { servers: mismatch }); const result = await provision({ @@ -199,7 +200,7 @@ export async function provisionPluginStorage(env: Env, plugins: Plugin[], log: L export function serverMismatch(adminUrl: string, webUrl: string | undefined): string | null { if (!webUrl) return null; const [admin, web] = [safeHostPort(adminUrl), safeHostPort(webUrl)]; - if (admin === null || web === null || admin === web) return null; // a malformed URL fails in config.ts + if (admin === null || web === null || admin === web) return null; // a malformed URL fails in assertUrl return `${admin} vs ${web}`; } diff --git a/src/config.test.ts b/src/config.test.ts index 62a00d5..dbd9b11 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -39,6 +39,29 @@ test("PLUGIN_DB_URL carrying credentials is refused, not silently overwritten", assert.equal(loadConfig({}).pluginDbUrl, undefined); // unset ⇒ storage off }); +// A DSN whose password breaks URL parsing (a bare "/") must not reach the boot error or the log. +// The credential-bearing keys get their own value; the rest share the two readers, so one loop covers them. +test("a malformed URL value is refused by naming the variable, never echoing the value", () => { + const bad: Record = { + APP_URL: "https://acme:pa/ss@[host", + HYDRA_ADMIN_URL: "http://u:pa/ss@[hydra:4445", + JWKS_URL: "http://u:pa/ss@[jwks", + KETO_READ_URL: "http://u:pa/ss@[keto:4466", + KETO_WRITE_URL: "http://u:pa/ss@[keto:4467", + KRATOS_ADMIN_URL: "http://u:pa/ss@[kratos:4434", + KRATOS_PUBLIC_URL: "http://u:pa/ss@[kratos:4433", + OTLP_ENDPOINT: "http://otel:pa/ss@[collector:4318", + PLUGIN_DB_URL: "postgres://admin:pa/ss@db:5432/plugins", + }; + for (const [key, value] of Object.entries(bad)) { + assert.throws( + () => loadConfig({ [key]: value }), + (err: Error) => err.message.includes(key) && !err.message.includes("pa/ss") && !err.message.includes(value), + key, + ); + } +}); + test("the per-role connection ceiling defaults to 10 and rejects nonsense", () => { assert.equal(resolvePluginDbConnectionLimit({}), 10); assert.equal(resolvePluginDbConnectionLimit({ PLUGIN_DB_CONNECTION_LIMIT: "25" }), 25); diff --git a/src/config.ts b/src/config.ts index dcceb5d..bfb81a9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -98,26 +98,28 @@ function readEnum(env: Env, key: string, allowed: readonly T[] throw new Error(`config: ${key} must be one of ${allowed.join(", ")}, got "${value}"`); } -// An optional absolute URL: unset/empty ⇒ undefined; a set-but-malformed value fails at boot. -function readOptionalUrl(env: Env, key: string): string | undefined { - const value = env[key] || undefined; - if (value === undefined) return undefined; +// Never echoes the value: a DSN whose password breaks URL parsing (a bare "/") would put the +// password in the boot error and the log. bootstrap uses it too, before the postgres driver, which +// rethrows a malformed DSN with the whole string attached. +export function assertUrl(key: string, value: string): void { try { new URL(value); } catch { - throw new Error(`config: ${key} is not a valid URL: ${value}`); + throw new Error(`config: ${key} must be an absolute URL (its value is not shown; percent-encode special characters in a password)`); } +} + +// An optional absolute URL: unset/empty ⇒ undefined; a set-but-malformed value fails at boot. +function readOptionalUrl(env: Env, key: string): string | undefined { + const value = env[key] || undefined; + if (value !== undefined) assertUrl(key, value); return value; } // An absolute URL: defaults to the Ory service; validated so a typo fails at boot. function readUrl(env: Env, key: string, devDefault: string): string { const value = env[key] ?? devDefault; - try { - new URL(value); - } catch { - throw new Error(`config: ${key} is not a valid URL: ${value}`); - } + assertUrl(key, value); return value; } diff --git a/todo.md b/todo.md index c52b36d..9426548 100644 --- a/todo.md +++ b/todo.md @@ -12,7 +12,6 @@ | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| -| 55 | 0.4.21 | | security | **Keep a URL config value out of the boot error that rejects it.** | 1 | 1 | 4 | 5 | 2 | | 0.0 | | 19 | 0.5.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | | 16 | 0.5.0 | | question | **Decide whether the single Keto `Resource` namespace splits into per-domain namespaces.** | 3 | 2 | 3 | 6 | 6 | 7 by 2 | 0.5 | | 48 | 0.5.0 | | | **Correct the Keto tuple in the `login.ts` and `bootstrap.ts` comments to the `granted` relation the code writes.** | 1 | 1 | 1 | 1 | 1 | code health by 4 | 28.7 | @@ -34,20 +33,6 @@ ## Details -### 55. Keep a URL config value out of the boot error that rejects it. - -`readOptionalUrl` and `readUrl` in `src/config.ts` throw `config: ${key} is not a valid URL: ${value}`, -so a malformed value reaches the boot error and the log. A DSN whose password breaks parsing, such as -`postgres://admin:pa/ss@db:5432/plugins`, leaks that password. The keys that can carry credentials: -`PLUGIN_DB_URL` (`readCredentiallessUrl` refuses userinfo only after `readOptionalUrl` has parsed it, -so a pasted admin DSN that fails to parse is echoed), and `OTLP_ENDPOINT`, whose collector URL may -embed basic-auth userinfo. The other URL keys, `APP_URL`, `HYDRA_ADMIN_URL`, `JWKS_URL`, -`KETO_READ_URL`, `KETO_WRITE_URL`, `KRATOS_ADMIN_URL` and `KRATOS_PUBLIC_URL`, pass through the same -two readers, so the fix covers them too. Name the variable and what it accepts, never the value, and -add a test per credential-bearing key. The other readers in `config.ts` echo only non-credential -values. `PLUGIN_DB_ADMIN_URL` isn't read in `config.ts`: `bootstrap.ts` hands it straight to -`postgres()`. Check whether the driver echoes a malformed one. - ### 19. Run the documented Docker commands on macOS and fix whatever misbehaves. Needs a Mac. Two suspects, both from `--user "$(id -u):$(id -g)"`: a macOS `id -g` is `20`, which is -- 2.52.0 From b8686377c9db2c641d798b71c63f6c2ab81753ee Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 13:49:38 +0100 Subject: [PATCH 2/6] =?UTF-8?q?Answer=20security=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=200=20=F0=9F=9F=A1=20(0=20misread);=20/security-r?= =?UTF-8?q?eview=20found=20nothing,=20and=20the=20full=20scan=20didn't=20f?= =?UTF-8?q?ire:=20the=20change=20removes=20a=20leak=20and=20opens=20no=20b?= =?UTF-8?q?oundary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0 From 8ffbb0b70dd2ed6d01e0b2158664f65e04aba7f3 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 13:56:46 +0100 Subject: [PATCH 3/6] =?UTF-8?q?Answer=20stability=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=204=20=F0=9F=9F=A1=20(0=20misread);=20the=20drive?= =?UTF-8?q?r=20decides=20what=20parses=20so=20a=20multi-host=20DSN=20still?= =?UTF-8?q?=20works,=20and=20the=20nine-key=20test=20shrinks=20to=20four?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++-- src/auth/bootstrap.test.ts | 10 ---------- src/auth/bootstrap.ts | 5 ++--- src/config.test.ts | 8 +------- src/config.ts | 5 ++--- src/plugin-host/storage-provisioning.ts | 12 +++++++++++- src/plugin-host/storage.test.ts | 7 +++++++ 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d3b46..2281811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,9 @@ at boot. Entries start at 0.3.0. ## Unreleased -### A malformed URL setting's boot error no longer prints the value +### A malformed URL variable's boot error no longer prints the value -When a URL setting such as `PLUGIN_DB_URL`, `PLUGIN_DB_ADMIN_URL` or `OTLP_ENDPOINT` can't be parsed, +When a URL variable such as `PLUGIN_DB_URL`, `PLUGIN_DB_ADMIN_URL` or `OTLP_ENDPOINT` can't be parsed, the boot error names the variable and not its value, so a DSN whose password breaks parsing doesn't put the password in the log. diff --git a/src/auth/bootstrap.test.ts b/src/auth/bootstrap.test.ts index 5040804..f57655b 100644 --- a/src/auth/bootstrap.test.ts +++ b/src/auth/bootstrap.test.ts @@ -181,16 +181,6 @@ test("provisioning still runs with nothing to provision, so orphans are reported assert.deepEqual(calls[0]?.pluginIds, []); }); -// The postgres driver rethrows a malformed DSN with the whole string on the error (`input`), so -// the URL is checked here, before the driver sees it. -test("a malformed PLUGIN_DB_ADMIN_URL is refused by name, never echoing the password", async () => { - const env = { PLUGIN_DB_ADMIN_URL: "postgres://admin:pa/ss@db:5432/plugins" }; - await assert.rejects( - provisionPluginStorage(env, [storagePlugin("things")], SILENT, async () => ({ orphans: [], provisioned: [] })), - (err: Error) => err.message.includes("PLUGIN_DB_ADMIN_URL") && !err.message.includes("pa/ss"), - ); -}); - test("a plugin declaring storage without a provisioning DSN fails loud, naming the plugin", async () => { const { calls, provision } = recordingProvisioner(); await assert.rejects( diff --git a/src/auth/bootstrap.ts b/src/auth/bootstrap.ts index 2e3c732..137ef59 100644 --- a/src/auth/bootstrap.ts +++ b/src/auth/bootstrap.ts @@ -8,7 +8,7 @@ // Then prints a first-run banner; fails loud on any unexpected upstream error. import { existsSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; -import { assertUrl, resolvePluginDbConnectionLimit, resolvePluginDbSecret } from "../config.ts"; +import { resolvePluginDbConnectionLimit, resolvePluginDbSecret } from "../config.ts"; import { discoverPlugins } from "../plugin-host/discovery.ts"; import { declaredPermissions, isValidPermissionName, type Plugin } from "../plugin-host/plugin.ts"; import { provisionStorage } from "../plugin-host/storage-provisioning.ts"; @@ -179,7 +179,6 @@ export async function provisionPluginStorage(env: Env, plugins: Plugin[], log: L // Provisioned here, connected to from web: a different server means the role is created in one // place and looked for in another, surfacing inside a plugin as "password authentication failed". // Warned, not refused — web reaching a pooler that cannot run CREATE DATABASE is a legitimate split. - assertUrl("PLUGIN_DB_ADMIN_URL", adminUrl); const mismatch = serverMismatch(adminUrl, env["PLUGIN_DB_URL"]); if (mismatch) log.warn("PLUGIN_DB_ADMIN_URL and PLUGIN_DB_URL name different servers", { servers: mismatch }); const result = await provision({ @@ -200,7 +199,7 @@ export async function provisionPluginStorage(env: Env, plugins: Plugin[], log: L export function serverMismatch(adminUrl: string, webUrl: string | undefined): string | null { if (!webUrl) return null; const [admin, web] = [safeHostPort(adminUrl), safeHostPort(webUrl)]; - if (admin === null || web === null || admin === web) return null; // a malformed URL fails in assertUrl + if (admin === null || web === null || admin === web) return null; // a malformed URL fails in config.ts return `${admin} vs ${web}`; } diff --git a/src/config.test.ts b/src/config.test.ts index dbd9b11..87a591a 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -39,16 +39,10 @@ test("PLUGIN_DB_URL carrying credentials is refused, not silently overwritten", assert.equal(loadConfig({}).pluginDbUrl, undefined); // unset ⇒ storage off }); -// A DSN whose password breaks URL parsing (a bare "/") must not reach the boot error or the log. -// The credential-bearing keys get their own value; the rest share the two readers, so one loop covers them. test("a malformed URL value is refused by naming the variable, never echoing the value", () => { + // PLUGIN_DB_URL and OTLP_ENDPOINT can carry credentials; APP_URL and KRATOS_PUBLIC_URL stand for the other keys, one per reader. const bad: Record = { APP_URL: "https://acme:pa/ss@[host", - HYDRA_ADMIN_URL: "http://u:pa/ss@[hydra:4445", - JWKS_URL: "http://u:pa/ss@[jwks", - KETO_READ_URL: "http://u:pa/ss@[keto:4466", - KETO_WRITE_URL: "http://u:pa/ss@[keto:4467", - KRATOS_ADMIN_URL: "http://u:pa/ss@[kratos:4434", KRATOS_PUBLIC_URL: "http://u:pa/ss@[kratos:4433", OTLP_ENDPOINT: "http://otel:pa/ss@[collector:4318", PLUGIN_DB_URL: "postgres://admin:pa/ss@db:5432/plugins", diff --git a/src/config.ts b/src/config.ts index bfb81a9..b14dde7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -99,9 +99,8 @@ function readEnum(env: Env, key: string, allowed: readonly T[] } // Never echoes the value: a DSN whose password breaks URL parsing (a bare "/") would put the -// password in the boot error and the log. bootstrap uses it too, before the postgres driver, which -// rethrows a malformed DSN with the whole string attached. -export function assertUrl(key: string, value: string): void { +// password in the boot error and the log. +function assertUrl(key: string, value: string): void { try { new URL(value); } catch { diff --git a/src/plugin-host/storage-provisioning.ts b/src/plugin-host/storage-provisioning.ts index c58d422..4c89b52 100644 --- a/src/plugin-host/storage-provisioning.ts +++ b/src/plugin-host/storage-provisioning.ts @@ -16,10 +16,20 @@ export interface ProvisionResult { provisioned: string[]; } +// The driver's error for a DSN it can't parse carries the whole string (`input`) — password included — +// so it is swallowed here. The driver, not `new URL`, decides what parses: it takes a multi-host DSN. +function connect(adminUrl: string): ReturnType { + try { + return postgres(adminUrl, { connect_timeout: 10, max: 1 }); + } catch { + throw new Error("PLUGIN_DB_ADMIN_URL must be a Postgres URL (its value is not shown; percent-encode special characters in a password)"); + } +} + export async function provisionStorage(options: ProvisionOptions): Promise { // Notices are left to surface: a REVOKE the account cannot perform only *warns*, and silencing // that would mean reporting a locked-down database that is still open to PUBLIC. - const sql = postgres(options.adminUrl, { connect_timeout: 10, max: 1 }); + const sql = connect(options.adminUrl); try { const provisioned: string[] = []; for (const pluginId of options.pluginIds) { diff --git a/src/plugin-host/storage.test.ts b/src/plugin-host/storage.test.ts index 3bc49c1..e34714d 100644 --- a/src/plugin-host/storage.test.ts +++ b/src/plugin-host/storage.test.ts @@ -108,6 +108,13 @@ test("a connection limit that is not a positive integer is refused, not interpol } }); +test("a malformed admin DSN is refused by name, without echoing its password", async () => { + await assert.rejects( + provisionStorage({ adminUrl: "postgres://admin:pa/ss@db:5432/plugins", connectionLimit: 10, pluginIds: [], secret: SECRET }), + (err: Error) => err.message.includes("PLUGIN_DB_ADMIN_URL") && !err.message.includes("pa/ss"), + ); +}); + // --- Integration: the statements above, against a real Postgres ----------------------- // Opt-in via PLUGIN_DB_ADMIN_URL (a superuser DSN); the unit gate runs no Postgres. What the unit // tests cannot prove lives here: the owner may create tables, and a peer role is locked out. -- 2.52.0 From 04545274f36790699ba589e91b5ced6a4f7390c1 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 13:56:46 +0100 Subject: [PATCH 4/6] =?UTF-8?q?Answer=20product-owner=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=200=20=F0=9F=9F=A1=20(0=20misread);=20approved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0 From 8e73d165da47562d3c476ea7721ac368951e2d62 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 13:56:46 +0100 Subject: [PATCH 5/6] =?UTF-8?q?Answer=20prose=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=205=20=F0=9F=9F=A1=20(0=20misread);=20the=20comme?= =?UTF-8?q?nts=20move=20to=20the=20guard,=20the=20changelog=20says=20varia?= =?UTF-8?q?ble?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.52.0 From c2389f871d5af3e0ce1e53b3c13d8ea8ec7a544b Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 14:09:54 +0100 Subject: [PATCH 6/6] Record the comprehension check: overall 6.50 then 6.38, under the 7.0 floor; the README's REQUIRE_SECURE_SECRETS drift is filed as item 56 --- docs/comprehension-history.md | 68 +++++++++++++++++++++++++++++++++++ todo.md | 14 +++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/comprehension-history.md b/docs/comprehension-history.md index 9e4e47f..49719f0 100644 --- a/docs/comprehension-history.md +++ b/docs/comprehension-history.md @@ -222,3 +222,71 @@ Inherited architect, would restructure first: - Locality, Overall: `src/http/app.ts:185` `handleRequest`: the most concerns and the highest complexity, already grandfathered, and every request-wide concern lands in it - Shape, Navigation: `src/ui/stylesheet-floor.ts:15` `FLOOR`: the 3am rule's home is a test-only module under `ui/` - Shape: `src/auth/routes.ts:275` `buildAuthRoutes`: the 19-module `src/auth/` mixes six concerns, and this file with `flow-view.ts` is where the next flow lands + +## 2026-10-31T13:06:16Z, PR #202 at 8e73d16 + +| Seat | Navigation | Locality | Shape | Self-sufficiency | Overall | +|---|---|---|---|---|---| +| Junior A | 7 | 6 | 6.5 | 6.5 | 6.5 | +| Mid A | 7 | 6 | 6 | 6.5 | 6.5 | +| Maintainability senior | 7 | 6 | 6 | 6.5 | 6.5 | +| Inherited architect | 6.5 | 6 | 6 | 6.5 | 6.5 | +| Mean | 6.88 | 6.00 | 6.13 | 6.50 | 6.50 | + +Junior A, hardest first: + +- Locality, Overall: `src/http/app.ts:185` `handleRequest`: about 165 lines in a fixed order that only a top-to-bottom read shows +- Self-sufficiency, Shape: `src/auth/login.ts:40` `readPermissions`: the comment says `#members` where the code uses `granted` +- Self-sufficiency: `src/plugin-host/settings.ts:35` `SettingsOf`: nested conditional types that need the charter's rule to decode + +Mid A, hardest first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: complexity 43 inside the 318-line `createApp` closure, and `switchBase` reads `builtinRoutes` declared 60 lines later +- Locality, Shape: `src/ui/stylesheet-floor.ts:119` `positive`: a regex CSS walker whose how stayed opaque +- Self-sufficiency: `src/auth/bootstrap.ts:31` `permissionTuple`: the comment says `#members` where the code uses `granted` + +Maintainability senior, hardest first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: 167 lines and complexity 43 mixing twelve concerns whose order no type expresses +- Locality: `src/ui/stylesheet-floor.ts:119` `positive`: a hand-rolled regex CSS scanner traced by hand +- Self-sufficiency: `src/auth/gate.ts:13` `allows`: an empty gate is allowed, and the reason lives only in AGENTS.md + +Inherited architect, would restructure first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: the largest hub, where every request-wide concern lands +- Shape, Navigation: `src/auth/routes.ts:275` `buildAuthRoutes`: the 19-module `src/auth/` mixes four concerns with no sub-folder +- Shape, Navigation: `src/ui/stylesheet-floor.ts:30` `floorViolations`: test tooling sits among the runtime UI modules + +## 2026-10-31T13:06:21Z, PR #202 rerun at 8e73d16 + +| Seat | Navigation | Locality | Shape | Self-sufficiency | Overall | +|---|---|---|---|---|---| +| Junior A | 7 | 5.5 | 6 | 6 | 6 | +| Mid A | 7 | 6 | 6 | 6 | 6.5 | +| Maintainability senior | 7 | 6 | 6 | 6 | 6.5 | +| Inherited architect | 7 | 5.5 | 6 | 7 | 6.5 | +| Mean | 7.00 | 5.75 | 6.00 | 6.25 | 6.38 | + +Junior A, hardest first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: closures over request state in a 318-line `createApp`, and it stayed opaque +- Self-sufficiency: `src/auth/routes.ts:58` `flowFailure`: the Kratos status semantics live outside the repo +- Self-sufficiency, Shape: `src/auth/login.ts:45` `readPermissions`: the comment says `#members` where the code uses `granted` + +Mid A, hardest first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: one pipeline whose steps mutate what later steps read +- Locality: `src/ui/stylesheet-floor.ts:119` `positive`: traced by hand +- Self-sufficiency, Shape: `src/auth/login.ts:45` `readPermissions`: the comment says `#members` where the code uses `granted` + +Maintainability senior, hardest first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: an unstated, load-bearing step order in shared closure state +- Locality: `src/ui/stylesheet-floor.ts:119` `positive`: a hand-rolled CSS scanner built on lookbehind regexes +- Self-sufficiency, Shape: `src/auth/bootstrap.ts:31` `permissionTuple`: the comment is false on the security-relevant path + +Inherited architect, would restructure first: + +- Locality, Shape, Overall: `src/http/app.ts:185` `handleRequest`: stage order lives only in line order, in a grandfathered complexity-43 function +- Shape, Navigation: `src/auth/routes.ts:275` `buildAuthRoutes`: 19 flat modules in `src/auth/`, and the next flow lands here +- Shape, Navigation: `src/ui/stylesheet-floor.ts:15` `FLOOR`: the 3am rule's home is a test-only module under `ui/` diff --git a/todo.md b/todo.md index 9426548..ec3053a 100644 --- a/todo.md +++ b/todo.md @@ -6,12 +6,13 @@ `Bar = 1` -`Next ID = 56` +`Next ID = 57` ## Items | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| +| 56 | 0.4.22 | | | **Correct the README's two statements that `REQUIRE_SECURE_SECRETS` guards only `CSRF_SECRET`.** | 1 | 2 | 1 | 2 | 1 | code health by 2 | 14.4 | | 19 | 0.5.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | | 16 | 0.5.0 | | question | **Decide whether the single Keto `Resource` namespace splits into per-domain namespaces.** | 3 | 2 | 3 | 6 | 6 | 7 by 2 | 0.5 | | 48 | 0.5.0 | | | **Correct the Keto tuple in the `login.ts` and `bootstrap.ts` comments to the `granted` relation the code writes.** | 1 | 1 | 1 | 1 | 1 | code health by 4 | 28.7 | @@ -33,6 +34,17 @@ ## Details +### 56. Correct the README's two statements that `REQUIRE_SECURE_SECRETS` guards only `CSRF_SECRET`. + +README → What you must supply (about `:1275`: "refuses to boot on a missing or throwaway +`CSRF_SECRET` and **nothing else**") and README → Security model (about `:1442`: +"`REQUIRE_SECURE_SECRETS` guards only `CSRF_SECRET`") contradict the Configuration table row (about +`:1216`), `src/config.ts:59` and `:190`, and `src/plugin-host/settings.ts`. The flag also refuses a +missing or throwaway `PLUGIN_DB_SECRET` once `PLUGIN_DB_URL` is set, and every plugin setting +declared `secret`. An operator following the checklist underrates the toggle. Make both passages say +what the flag enforces by pointing to the table row, its one home. Four comprehension seats raised it +independently. Markdown only. + ### 19. Run the documented Docker commands on macOS and fix whatever misbehaves. Needs a Mac. Two suspects, both from `--user "$(id -u):$(id -g)"`: a macOS `id -g` is `20`, which is -- 2.52.0