Startup database failure names the input and a code, not the URL's parts
The entrypoint harness showed pg printing a piece of the database URL file on a failed startup: `getaddrinfo ENOTFOUND <host>` for an unresolvable host. A password with an unescaped delimiter moves part of itself into the host or the database name, and the resolver or server error then prints it. The only thing keeping a malformed URL out of stderr was pg dropping Node's `input` property (Soul S5). createStore now turns a schema-preparation failure into one error naming GC_ACCESS_DATABASE_URL_FILE and the error code, with no cause. The harness cases expect that message, and a new case puts the canary in the host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
83d2f48256
commit
376dfbd2c2
2 changed files with 23 additions and 5 deletions
|
|
@ -14,7 +14,18 @@ export async function createStore(config: HeimdallConfig): Promise<HeimdallStore
|
|||
: new InMemoryStore();
|
||||
|
||||
if (config.storage.applySchemaOnStartup) {
|
||||
await store.ensureSchema();
|
||||
try {
|
||||
await store.ensureSchema();
|
||||
} catch (error) {
|
||||
// pg's and Node's errors name the host, the database or the URL itself,
|
||||
// and any of those can hold part of a secret that was bound or escaped
|
||||
// wrongly. Startup prints this error, so it names only the input and a
|
||||
// code, and carries no cause.
|
||||
const code = (error as { code?: unknown }).code;
|
||||
throw new Error(
|
||||
`Postgres storage could not be prepared (${typeof code === "string" ? code : "error"}); check GC_ACCESS_DATABASE_URL_FILE.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return store;
|
||||
|
|
|
|||
|
|
@ -112,22 +112,29 @@ const cases: Case[] = [
|
|||
{
|
||||
name: "a database URL with an unusable port",
|
||||
env: () => ({ GC_ACCESS_DATABASE_URL_FILE: file("db-port", `postgres://heimdall:${CANARY}port@db:notaport/heimdall\n`) }),
|
||||
expect: /./,
|
||||
expect: /Postgres storage could not be prepared \(ERR_INVALID_URL\)/,
|
||||
},
|
||||
{
|
||||
name: "a database URL that does not parse",
|
||||
env: () => ({ GC_ACCESS_DATABASE_URL_FILE: file("db-parse", `postgres://heimdall:${CANARY} parse@[bad/heimdall\n`) }),
|
||||
expect: /./,
|
||||
expect: /Postgres storage could not be prepared \(E[A-Z_]+\)/,
|
||||
},
|
||||
{
|
||||
name: "a database URL with a broken percent escape",
|
||||
env: () => ({ GC_ACCESS_DATABASE_URL_FILE: file("db-percent", `postgres://heimdall:${CANARY}%zz@127.0.0.1:1/heimdall\n`) }),
|
||||
expect: /./,
|
||||
expect: /Postgres storage could not be prepared \(ECONNREFUSED\)/,
|
||||
},
|
||||
{
|
||||
name: "a database that refuses the connection",
|
||||
env: () => ({ GC_ACCESS_DATABASE_URL_FILE: file("db-refused", `postgres://heimdall:${CANARY}refused@127.0.0.1:1/heimdall\n`) }),
|
||||
expect: /ECONNREFUSED/,
|
||||
expect: /Postgres storage could not be prepared \(ECONNREFUSED\)/,
|
||||
},
|
||||
{
|
||||
// A password holding an unescaped delimiter moves part of it into the
|
||||
// host, and the resolver's error names the host.
|
||||
name: "a database host that does not resolve",
|
||||
env: () => ({ GC_ACCESS_DATABASE_URL_FILE: file("db-host", `postgres://heimdall:pw@${CANARY}host.invalid/heimdall\n`) }),
|
||||
expect: /Postgres storage could not be prepared \(E[A-Z_]+\)/,
|
||||
},
|
||||
{
|
||||
name: "private key text bound to the signing key path",
|
||||
|
|
|
|||
Loading…
Reference in a new issue