Confine the Postgres driver to bootstrap, bound plugin connections, and gate the storage DDL
CI / full-gate (push) Failing after 23s

This commit is contained in:
2026-08-19 00:08:35 +02:00
parent bba048e38f
commit ae8f105360
11 changed files with 313 additions and 129 deletions
+55 -37
View File
@@ -8,12 +8,15 @@
// 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 { resolvePluginDbSecret } from "../config.ts";
import { resolvePluginDbConnectionLimit, resolvePluginDbSecret } from "../config.ts";
import { discoverPlugins } from "../plugin-host/discovery.ts";
import { declaredPermissions, isValidPermissionName } from "../plugin-host/plugin.ts";
import { provisionStorage } from "../plugin-host/storage.ts";
import { declaredPermissions, isValidPermissionName, type Plugin } from "../plugin-host/plugin.ts";
import { provisionStorage } from "../plugin-host/storage-provisioning.ts";
import { storagePluginIds } from "../plugin-host/storage.ts";
import { generateJwks, type JwkSet } from "./gen-jwks.ts";
import { createLogger, runWithLog, tracedFetch } from "../logger.ts";
import { createLogger, runWithLog, tracedFetch, type Log } from "../logger.ts";
type Env = Record<string, string | undefined>;
// --- Pure payload builders (the Kratos/Keto request contracts) -----------------------
@@ -152,42 +155,57 @@ async function main() {
// runWithLog makes `log` ambient so seedAdmin's tracedFetch traces the Kratos/Keto seed calls.
await runWithLog(log, async () => {
if (ensureJwks(env["JWKS_FILE"] ?? "/etc/config/kratos/tokenizer/jwks.json")) log.info("generated a JWKS signing key");
const plugins = await discoverPlugins();
// A database and login role for each plugin that asked for one. It happens here because
// bootstrap holds the stack's only superuser credentials — web derives the same password from
// PLUGIN_DB_SECRET and connects as the plugin's own role, never as a superuser.
const storagePlugins = plugins.filter((plugin) => plugin.storage).map((plugin) => plugin.id);
if (storagePlugins.length > 0) {
const adminUrl = env["PLUGIN_DB_ADMIN_URL"];
if (!adminUrl) throw new Error(`bootstrap: PLUGIN_DB_ADMIN_URL must be set — these plugins declare storage: ${storagePlugins.join(", ")}`);
const provisioned = await provisionStorage({ adminUrl, pluginIds: storagePlugins, secret: resolvePluginDbSecret(env) });
log.info("plugin storage provisioned", { databases: provisioned.join(", ") });
}
// Seed every discovered plugin's declared permission names (plus any ADMIN_PERMISSIONS), so the
// shipped example — and any dropped-in plugin — works for the demo admin without a host edit.
const declared = declaredPermissions(plugins).map((decl) => decl.name);
const { ignored, permissions } = seedPermissions(env["ADMIN_PERMISSIONS"], declared);
if (ignored.length > 0) {
log.warn("ignoring ADMIN_PERMISSIONS entries that are not <resource>:<action>", { ignored: ignored.join(", ") });
}
const email = env["ADMIN_EMAIL"] ?? "admin@plainpages.local";
const password = env["ADMIN_PASSWORD"] ?? "admin";
const result = await seedAdmin({
email,
fetchImpl: tracedFetch,
ketoWriteUrl: env["KETO_WRITE_URL"] ?? "http://keto:4467",
kratosAdminUrl: env["KRATOS_ADMIN_URL"] ?? "http://kratos:4434",
password,
permissions,
});
log.info("admin seeded", { created: result.created, id: result.id, permissions: result.permissions.join(", ") });
// The banner is human-facing UX (the first-run "you're ready" block), not a log event — print raw.
console.log(firstRunBanner({ appUrl: env["APP_URL"] ?? "http://localhost:3000", email, password }));
await provisionPluginStorage(env, plugins, log);
await seedAdminAndPermissions(env, plugins, log);
});
await log.end(); // flush any pending OTLP spans/logs before the one-shot exits
}
// A database and login role for each plugin that asked for one. It happens here because bootstrap
// holds the stack's only provisioning credentials — web derives the same password and connects as
// the plugin's own role.
async function provisionPluginStorage(env: Env, plugins: Plugin[], log: Log): Promise<void> {
const ids = storagePluginIds(plugins);
const adminUrl = env["PLUGIN_DB_ADMIN_URL"];
// Still connect with nothing to provision, as long as storage is configured: uninstalling the
// last storage plugin is exactly when an orphaned database needs naming.
if (ids.length === 0 && !adminUrl) return;
if (!adminUrl) throw new Error(`bootstrap: PLUGIN_DB_ADMIN_URL must be set — these plugins declare storage: ${ids.join(", ")}`);
const result = await provisionStorage({
adminUrl,
connectionLimit: resolvePluginDbConnectionLimit(env),
pluginIds: ids,
secret: resolvePluginDbSecret(env),
});
if (result.provisioned.length > 0) log.info("plugin storage provisioned", { databases: result.provisioned.join(", ") });
// Never dropped, so an uninstalled plugin's data outlives it — say so, or nobody can find it.
if (result.orphans.length > 0) {
log.warn("plugin databases no installed plugin claims", { databases: result.orphans.join(", ") });
}
}
// Seed every discovered plugin's declared permission names (plus any ADMIN_PERMISSIONS), so the
// shipped example — and any dropped-in plugin — works for the demo admin without a host edit.
async function seedAdminAndPermissions(env: Env, plugins: Plugin[], log: Log): Promise<void> {
const declared = declaredPermissions(plugins).map((decl) => decl.name);
const { ignored, permissions } = seedPermissions(env["ADMIN_PERMISSIONS"], declared);
if (ignored.length > 0) {
log.warn("ignoring ADMIN_PERMISSIONS entries that are not <resource>:<action>", { ignored: ignored.join(", ") });
}
const email = env["ADMIN_EMAIL"] ?? "admin@plainpages.local";
const password = env["ADMIN_PASSWORD"] ?? "admin";
const result = await seedAdmin({
email,
fetchImpl: tracedFetch,
ketoWriteUrl: env["KETO_WRITE_URL"] ?? "http://keto:4467",
kratosAdminUrl: env["KRATOS_ADMIN_URL"] ?? "http://kratos:4434",
password,
permissions,
});
log.info("admin seeded", { created: result.created, id: result.id, permissions: result.permissions.join(", ") });
// The banner is human-facing UX (the first-run "you're ready" block), not a log event — print raw.
console.log(firstRunBanner({ appUrl: env["APP_URL"] ?? "http://localhost:3000", email, password }));
}
if (process.argv[1] === fileURLToPath(import.meta.url)) await main();
+19 -1
View File
@@ -17,6 +17,24 @@ export function resolvePluginDbSecret(env: Env, enforce?: boolean): string {
return readSecret(env, "PLUGIN_DB_SECRET", DEV_PLUGIN_DB_SECRET, enforce ?? readBool(env, "REQUIRE_SECURE_SECRETS", false));
}
// Only bootstrap provisions, so only bootstrap reads this; env still gets read in one place.
export function resolvePluginDbConnectionLimit(env: Env): number {
return readPosInt(env, "PLUGIN_DB_CONNECTION_LIMIT", 10);
}
// PLUGIN_DB_URL is web's, and web must never hold credentials that outrank a plugin's own role.
// Pasting the admin DSN here would otherwise work — buildCredentials overwrites the userinfo — and
// leave a superuser password in the environment plugin code can read.
function readCredentiallessUrl(env: Env, key: string): string | undefined {
const value = readOptionalUrl(env, key);
if (value === undefined) return undefined;
const url = new URL(value);
if (url.username || url.password) {
throw new Error(`config: ${key} must carry no username or password — each plugin connects as its own role`);
}
return value;
}
export interface Config {
appUrl: string | undefined; // canonical public URL; set ⇒ off-host visitors are redirected here. Unset ⇒ no redirect (explicit toggle)
cacheTemplates: boolean;
@@ -168,7 +186,7 @@ export function loadConfig(env: Env = process.env): Config {
// out of web's environment. Unset ⇒ storage is off and a plugin declaring it fails loud at boot,
// which is also why the secret is only enforced once a URL is configured.
pluginDbSecret: resolvePluginDbSecret(env, requireSecure && Boolean(env["PLUGIN_DB_URL"])),
pluginDbUrl: readOptionalUrl(env, "PLUGIN_DB_URL"),
pluginDbUrl: readCredentiallessUrl(env, "PLUGIN_DB_URL"),
port: readPort(env),
// Optional instant-revoke, off by default. When on, an admin deactivate/delete or permission
// change revokes the subject's live tokens at once; the entry lives ttl seconds (≥ the 10m
+45
View File
@@ -0,0 +1,45 @@
// The connecting half of plugin storage: runs the DDL storage.ts plans. Imported by bootstrap
// alone — the only process holding superuser credentials, which is why the driver stops here.
import postgres from "postgres";
import { derivePassword, NAME_PREFIX, provisionSql, storageName } from "./storage.ts";
export interface ProvisionOptions {
adminUrl: string; // needs CREATEDB + CREATEROLE, not superuser
connectionLimit: number;
pluginIds: string[];
secret: string;
}
export interface ProvisionResult {
orphans: string[]; // provisioned once, but no installed plugin claims them any more
provisioned: string[];
}
// Idempotent, and it drops nothing: an uninstalled plugin keeps its data until an operator removes
// it deliberately. Orphans are reported rather than removed, so nobody has to guess they exist.
export async function provisionStorage(options: ProvisionOptions): Promise<ProvisionResult> {
const sql = postgres(options.adminUrl, { connect_timeout: 10, max: 1, onnotice: () => {} });
try {
const provisioned: string[] = [];
for (const pluginId of options.pluginIds) {
const name = storageName(pluginId);
const [role] = await sql`SELECT 1 FROM pg_roles WHERE rolname = ${name}`;
const [database] = await sql`SELECT 1 FROM pg_database WHERE datname = ${name}`;
const plan = provisionSql({
connectionLimit: options.connectionLimit,
databaseExists: database !== undefined,
name,
password: derivePassword(options.secret, pluginId),
roleExists: role !== undefined,
});
for (const statement of plan) await sql.unsafe(statement); // provisionSql quotes what it interpolates
provisioned.push(name);
}
const existing = await sql<{ datname: string }[]>`SELECT datname FROM pg_database WHERE starts_with(datname, ${NAME_PREFIX})`;
const orphans = existing.map((row) => row.datname).filter((name) => !provisioned.includes(name)).sort();
return { orphans, provisioned };
} finally {
await sql.end();
}
}
+36 -8
View File
@@ -4,16 +4,17 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import postgres from "postgres";
import { provisionStorage } from "./storage-provisioning.ts";
import {
buildCredentials,
derivePassword,
isValidStoragePluginId,
MAX_STORAGE_PLUGIN_ID,
provisionSql,
provisionStorage,
quoteIdentifier,
quoteLiteral,
storageName,
storagePluginIds,
} from "./storage.ts";
const SECRET = "a-test-secret";
@@ -61,23 +62,42 @@ test("quoting doubles an embedded quote", () => {
assert.equal(quoteLiteral("we'ird"), "'we''ird'");
});
const ATTRIBUTES = "LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE CONNECTION LIMIT 10";
test("only the plugins that asked for storage are provisioned", () => {
assert.deepEqual(
storagePluginIds([{ apiVersion: "1.0.0", id: "a", storage: true }, { apiVersion: "1.0.0", id: "b" }, { apiVersion: "1.0.0", id: "c", storage: true }]),
["a", "c"],
);
});
test("provisioning creates the role and the database when neither exists", () => {
assert.deepEqual(provisionSql("plugin_things", "pw", { databaseExists: false, roleExists: false }), [
`CREATE ROLE "plugin_things" LOGIN PASSWORD 'pw'`,
const plan = { connectionLimit: 10, databaseExists: false, name: "plugin_things", password: "pw", roleExists: false };
assert.deepEqual(provisionSql(plan), [
`CREATE ROLE "plugin_things" ${ATTRIBUTES} PASSWORD 'pw'`,
`CREATE DATABASE "plugin_things" OWNER "plugin_things"`,
`REVOKE ALL ON DATABASE "plugin_things" FROM PUBLIC`,
`GRANT ALL PRIVILEGES ON DATABASE "plugin_things" TO "plugin_things"`,
]);
});
test("re-provisioning re-sets the password and creates nothing twice", () => {
assert.deepEqual(provisionSql("plugin_things", "rotated", { databaseExists: true, roleExists: true }), [
`ALTER ROLE "plugin_things" WITH LOGIN PASSWORD 'rotated'`,
// Re-asserting the attributes, not just the password, is what makes "idempotent" mean the role
// cannot drift — a CREATEDB granted by hand out of band is taken back on the next boot.
test("re-provisioning re-asserts every attribute and creates nothing twice", () => {
const plan = { connectionLimit: 10, databaseExists: true, name: "plugin_things", password: "rotated", roleExists: true };
assert.deepEqual(provisionSql(plan), [
`ALTER ROLE "plugin_things" WITH ${ATTRIBUTES} PASSWORD 'rotated'`,
`REVOKE ALL ON DATABASE "plugin_things" FROM PUBLIC`,
`GRANT ALL PRIVILEGES ON DATABASE "plugin_things" TO "plugin_things"`,
]);
});
// The limit is interpolated unquoted, so a non-integer would corrupt the statement text.
test("a non-integer connection limit is refused, not interpolated", () => {
const plan = { connectionLimit: 1.5, databaseExists: false, name: "plugin_things", password: "pw", roleExists: false };
assert.throws(() => provisionSql(plan), /connectionLimit must be an integer/);
});
// --- 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.
@@ -107,7 +127,7 @@ test("provisions a database its plugin can use and a peer plugin cannot reach",
const base = baseUrlOf(ADMIN_URL);
const admin = postgres(ADMIN_URL, { connect_timeout: 10, max: 1, onnotice: () => {} });
try {
await provisionStorage({ adminUrl: ADMIN_URL, pluginIds: ids, secret: SECRET });
await provisionStorage({ adminUrl: ADMIN_URL, connectionLimit: 10, pluginIds: ids, secret: SECRET });
const owner = buildCredentials(base, "storage-itest-a", SECRET);
await queryAs(owner.url, "CREATE TABLE IF NOT EXISTS notes (body text)");
@@ -121,11 +141,19 @@ test("provisions a database its plugin can use and a peer plugin cannot reach",
await assert.rejects(queryAs(peer.href, "SELECT 1"), /permission denied|not permitted/i);
// Re-running is idempotent, and a rotated secret lands on the existing role.
await provisionStorage({ adminUrl: ADMIN_URL, pluginIds: ids, secret: "a-rotated-secret" });
const rerun = await provisionStorage({ adminUrl: ADMIN_URL, connectionLimit: 10, pluginIds: ids, secret: "a-rotated-secret" });
// Scoped to this test's own ids: another plugin's database on the same server is not this
// test's business, and asserting otherwise would make the suite order-dependent.
for (const id of ids) assert.ok(!rerun.orphans.includes(storageName(id)), `${id} is still installed`);
const rotated = buildCredentials(base, "storage-itest-a", "a-rotated-secret");
const kept = (await queryAs(rotated.url, "SELECT body FROM notes")) as { body: string }[];
assert.deepEqual(kept.map((row) => row.body), ["persisted"]); // rotating the secret keeps the data
await assert.rejects(queryAs(owner.url, "SELECT 1"), /password authentication failed/i);
// Uninstalling drops nothing, so what is left behind must be named — including when the LAST
// storage plugin goes and there is nothing left to provision.
const uninstalled = await provisionStorage({ adminUrl: ADMIN_URL, connectionLimit: 10, pluginIds: [], secret: "a-rotated-secret" });
for (const id of ids) assert.ok(uninstalled.orphans.includes(storageName(id)), `${id}'s database is reported`);
} finally {
for (const id of ids) {
const name = quoteIdentifier(storageName(id));
+24 -43
View File
@@ -1,12 +1,12 @@
// Per-plugin Postgres storage (README → Plugin storage). Only bootstrap provisions, because only it
// is given superuser credentials; web derives the same passwords and never sees them.
// Per-plugin Postgres storage: the naming, credential and DDL rules (README → Plugin storage).
// Pure — the connecting half lives in storage-provisioning.ts, so `web` never loads a driver.
import { createHmac } from "node:crypto";
import postgres from "postgres";
import type { Plugin } from "./plugin.ts";
// Database and role share one name, so reconnecting needs nothing looked up. The prefix also keeps
// a plugin id from ever naming an Ory database.
const NAME_PREFIX = "plugin_";
export const NAME_PREFIX = "plugin_";
// Postgres truncates an identifier at 63 bytes, which would silently collide two long ids.
export const MAX_STORAGE_PLUGIN_ID = 63 - NAME_PREFIX.length;
@@ -29,6 +29,10 @@ export function isValidStoragePluginId(pluginId: string): boolean {
return pluginId.length <= MAX_STORAGE_PLUGIN_ID;
}
export function storagePluginIds(plugins: Plugin[]): string[] {
return plugins.filter((plugin) => plugin.storage).map((plugin) => plugin.id);
}
// Derived, never stored — which is what keeps the host free of state it would have to persist.
export function derivePassword(secret: string, pluginId: string): string {
return createHmac("sha256", secret).update(pluginId).digest("base64url");
@@ -54,51 +58,28 @@ export function quoteLiteral(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
export interface ProvisionState {
export interface ProvisionPlan {
connectionLimit: number;
databaseExists: boolean;
name: string;
password: string;
roleExists: boolean;
}
// One plugin's full plan, in order. The password is re-set on every run so rotating the secret needs
// no separate step, and PUBLIC loses CONNECT so no other plugin's role can reach this database.
export function provisionSql(name: string, password: string, state: ProvisionState): string[] {
const identifier = quoteIdentifier(name);
const secret = quoteLiteral(password);
// One plugin's full plan, in order. Both role branches state the same attributes, so every boot
// re-asserts them: a privilege granted by hand out of band does not survive silently.
export function provisionSql(plan: ProvisionPlan): string[] {
if (!Number.isSafeInteger(plan.connectionLimit)) {
throw new Error(`storage: connectionLimit must be an integer, got ${plan.connectionLimit}`); // interpolated unquoted
}
const identifier = quoteIdentifier(plan.name);
// NOSUPERUSER/NOCREATEDB/NOCREATEROLE: a plugin owns its own database and nothing beyond it.
// The limit bounds one plugin's pools so they cannot starve Ory, which shares this server.
const attributes = `LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE CONNECTION LIMIT ${plan.connectionLimit} PASSWORD ${quoteLiteral(plan.password)}`;
return [
state.roleExists
? `ALTER ROLE ${identifier} WITH LOGIN PASSWORD ${secret}`
: `CREATE ROLE ${identifier} LOGIN PASSWORD ${secret}`,
...(state.databaseExists ? [] : [`CREATE DATABASE ${identifier} OWNER ${identifier}`]),
plan.roleExists ? `ALTER ROLE ${identifier} WITH ${attributes}` : `CREATE ROLE ${identifier} ${attributes}`,
...(plan.databaseExists ? [] : [`CREATE DATABASE ${identifier} OWNER ${identifier}`]),
`REVOKE ALL ON DATABASE ${identifier} FROM PUBLIC`,
`GRANT ALL PRIVILEGES ON DATABASE ${identifier} TO ${identifier}`,
];
}
export interface ProvisionOptions {
adminUrl: string; // superuser DSN — the rights to create a database and a role
pluginIds: string[];
secret: string;
}
// Idempotent, and it drops nothing: an uninstalled plugin keeps its data until an operator removes
// it deliberately.
export async function provisionStorage(options: ProvisionOptions): Promise<string[]> {
const sql = postgres(options.adminUrl, { connect_timeout: 10, max: 1, onnotice: () => {} });
try {
const names: string[] = [];
for (const pluginId of options.pluginIds) {
const name = storageName(pluginId);
const [role] = await sql`SELECT 1 FROM pg_roles WHERE rolname = ${name}`;
const [database] = await sql`SELECT 1 FROM pg_database WHERE datname = ${name}`;
const plan = provisionSql(name, derivePassword(options.secret, pluginId), {
databaseExists: database !== undefined,
roleExists: role !== undefined,
});
for (const statement of plan) await sql.unsafe(statement); // provisionSql quotes what it interpolates
names.push(name);
}
return names;
} finally {
await sql.end();
}
}
+30 -1
View File
@@ -3,11 +3,20 @@
// verified by booting postgres in CI/e2e; this catches edits.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { readdirSync, readFileSync } from "node:fs";
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
const ORY_DATABASES = ["hydra", "keto", "kratos"]; // one DB per Ory service
function sourceFiles(dir = "src"): string[] {
const out: string[] = [];
for (const entry of readdirSync(new URL(`../${dir}/`, import.meta.url), { withFileTypes: true })) {
if (entry.isDirectory()) out.push(...sourceFiles(`${dir}/${entry.name}`));
else if (entry.name.endsWith(".ts")) out.push(`${dir}/${entry.name}`);
}
return out.sort();
}
test("init SQL gives each Ory service its own database, and leaves plugin databases to bootstrap", () => {
const sql = read("ory/postgres/init/init.sql");
for (const db of ORY_DATABASES) {
@@ -16,4 +25,24 @@ test("init SQL gives each Ory service its own database, and leaves plugin databa
// This file runs once, on an empty data dir — a plugin database added here would never appear for
// a plugin dropped in later. bootstrap provisions them on every boot instead.
assert.doesNotMatch(sql, /plugin_/i, "no plugin database is seeded here");
// PUBLIC keeps CONNECT unless it is revoked, which would put every plugin role on the auth plane.
for (const db of ORY_DATABASES) {
assert.match(sql, new RegExp(`REVOKE CONNECT ON DATABASE ${db} FROM PUBLIC`, "i"), `${db} is closed to PUBLIC`);
}
});
// AGENTS.md records that the driver runs the provisioning DDL in bootstrap "and nothing else".
// discovery.ts once imported a validator from the module that held it, quietly putting the driver in
// web's graph — the claim outlived the fact, which is what this catches.
test("the Postgres driver reaches bootstrap only, never web's import graph", () => {
const files = sourceFiles();
assert.ok(files.length > 40, "walks the source tree");
assert.deepEqual(
files.filter((f) => /^import .*"postgres"/m.test(read(f))), // an import line, not a mention of one
["src/plugin-host/storage-provisioning.ts", "src/plugin-host/storage.test.ts"],
);
assert.deepEqual(
files.filter((f) => !f.endsWith(".test.ts") && /from "[^"]*storage-provisioning\.ts"/.test(read(f))),
["src/auth/bootstrap.ts"],
);
});
+15 -5
View File
@@ -13,7 +13,7 @@ import { createKratosAdmin } from "./auth/kratos-admin.ts";
import { createKratosPublic } from "./auth/kratos-public.ts";
import { createLogger, tracedFetch } from "./logger.ts";
import { loadMenuConfig } from "./ui/menu-config.ts";
import { buildCredentials } from "./plugin-host/storage.ts";
import { buildCredentials, storagePluginIds, type StorageCredentials } from "./plugin-host/storage.ts";
const config = loadConfig(); // validates the env (incl. enforced secrets) — fails loud at boot
// App-level logger: structured, OTLP-capable when OTLP_ENDPOINT is set. The hot path clones it
@@ -48,15 +48,25 @@ log.info("locales loaded", { locales: i18n.available.join(", ") });
// A plugin's database credentials are derived, never stored — so the only thing that can be missing
// is the server itself. Refuse at boot rather than at that plugin's first query, hours later.
const pluginDbUrl = config.pluginDbUrl;
const declaresStorage = plugins.filter((plugin) => plugin.storage).map((plugin) => plugin.id);
const declaresStorage = storagePluginIds(plugins);
if (declaresStorage.length > 0 && pluginDbUrl === undefined) {
throw new Error(`config: PLUGIN_DB_URL must be set — these plugins declare storage: ${declaresStorage.join(", ")}`);
}
// Derive every plugin's credentials first, then drop the secret: onBoot is the window in which
// plugin code could read it out of the environment and derive a *peer's* password. Order matters —
// deleting after the hooks would protect nothing. Defence in depth, not a boundary (AGENTS.md).
const storageCredentials = new Map<string, StorageCredentials>();
if (pluginDbUrl !== undefined) {
for (const id of declaresStorage) storageCredentials.set(id, buildCredentials(pluginDbUrl, id, config.pluginDbSecret));
}
delete process.env["PLUGIN_DB_SECRET"];
// plugin onBoot — after discovery, before listen; a throw aborts boot.
await runBootHooks(plugins, (plugin) =>
plugin.storage && pluginDbUrl !== undefined ? { storage: buildCredentials(pluginDbUrl, plugin.id, config.pluginDbSecret) } : {},
);
await runBootHooks(plugins, (plugin) => {
const storage = storageCredentials.get(plugin.id);
return storage ? { storage } : {};
});
const server = createApp({
// Canonical-host redirect target (off-host GET/HEAD visitors are sent here). Opt-in: omitted unless