fix: prevent migration timeout overrides
pg-connection-string retains arbitrary query keys, so the driver's own configuration key connectionTimeoutMillis reaches ConnectionParameters straight from a URL — and it was not on the rejected list, while spellings that do nothing were. Rejecting it closes that gap. The precedence claims that guard was built on were partly wrong, so every source was measured against the installed [email protected].0, a real blackhole listener, and a real PostgreSQL rather than inferred from the merge: statement_timeout=0 / =900000 server reported 0 and 15min — wins outright query_timeout=0 overrides the field, but the parsed value is the string "0", which is truthy, so every query gets a 0 ms read timer and fails nondeterministically — it breaks the client rather than loosening the bound connectionTimeoutMillis=0/900000 pollutes connectionParameters.connect_timeout (10 -> 0 or 900 s), but that field only feeds getLibpqConnectionString(), which the pure-JS client never calls; the effective timer was unchanged, with an explicit 500 ms still expiring at ~500 ms either way connect_timeout=0 no effect: recomputed from the explicit field options=-c statement_timeout=… no effect, including a backslash-escaped spelling — the named startup parameter wins PGOPTIONS / PGCONNECT_TIMEOUT no effect unrelated parameters no effect, deliberately untouched Only statement_timeout is a genuine downgrade. The rest are rejected as unsupported configuration and defense in depth: no legitimate DATABASE_URL carries a driver option, the polluted connect_timeout would take effect on the native binding, and query_timeout produces intermittent failures an operator could not diagnose. That distinction is now stated where it was previously asserted wrongly — the earlier comment and ADR text claimed connect_timeout overrode the explicit field, which measurement disproved. Rejection still happens at the one shared construction boundary, before any pg.Client exists, with the existing fixed POSTGRES_MIGRATION_CONFIGURATION_INVALID and no URL, host, port, credential, parameter, or value in the message. The 10 s defaults, narrower test overrides, Pass 2 TLS policy and its URL-conflict checks, and every unrelated parameter are unchanged. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
74e8840f0d
commit
6912680113
@@ -94,20 +94,46 @@ export const MIGRATION_CONNECTION_TIMEOUT_MS = 10_000;
|
||||
export const MIGRATION_OPERATION_TIMEOUT_MS = 10_000;
|
||||
|
||||
/**
|
||||
* Connection-string parameters the pinned `[email protected]` would let silently override the timeouts
|
||||
* selected above. Its `ConnectionParameters` merges as
|
||||
* `Object.assign({}, config, parse(config.connectionString))`, so the **URL wins** over explicitly
|
||||
* passed options — verified directly against the pinned driver, where an explicit
|
||||
* `statement_timeout: 10000` alongside `?statement_timeout=0` produced a server-reported
|
||||
* `statement_timeout` of `0`. Rejecting these is therefore not stylistic: without it the policy
|
||||
* above is advisory.
|
||||
* Connection-string parameters that must not reach the pinned driver, established by measuring the
|
||||
* installed `[email protected]`/`[email protected]` against a real PostgreSQL and a real
|
||||
* blackhole listener rather than by reading the merge and assuming.
|
||||
*
|
||||
* Deliberately narrow — only settings that actually conflict with the timeouts this boundary
|
||||
* selects. `options` is included solely because it can carry `-c statement_timeout=...`, and is
|
||||
* rejected only when it does.
|
||||
* `ConnectionParameters` merges as `Object.assign({}, config, parse(config.connectionString))` and
|
||||
* `pg-connection-string` retains *arbitrary* query keys, so any of these lands in the driver's
|
||||
* configuration. What each one actually does differs, and only the first genuinely weakens policy:
|
||||
*
|
||||
* - `statement_timeout` — **wins outright.** `?statement_timeout=0` produced a server-reported
|
||||
* `statement_timeout` of `0` (disabled) and `?statement_timeout=900000` produced `15min`, against
|
||||
* an explicit `statement_timeout: 10000`. This is the one real downgrade.
|
||||
* - `query_timeout` — overrides the explicit field, but does not weaken the bound; it breaks the
|
||||
* client. `client.query` computes its read timer as
|
||||
* `queryConfig.query_timeout || connectionParameters.query_timeout`, and the parsed URL value is
|
||||
* the *string* `"0"` — truthy — so every query gets a 0 ms read timer and fails
|
||||
* nondeterministically with `Query read timeout`. Rejected so an operator gets a fixed
|
||||
* configuration error instead of intermittent, unexplained query failures.
|
||||
* - `connectionTimeoutMillis` — pollutes `ConnectionParameters.connect_timeout` (10 → `0` or `900`
|
||||
* seconds), but that field is only consumed by `getLibpqConnectionString()`, which the pure-JS
|
||||
* client never calls. Measured against a blackhole listener, the effective connect timer is
|
||||
* **unchanged**: an explicit 500 ms still expired at ~500 ms with `?connectionTimeoutMillis=0`
|
||||
* and with `=900000`. Rejected as unsupported and as defense in depth — no legitimate
|
||||
* `DATABASE_URL` carries a driver option, and the polluted value would take effect on the native
|
||||
* binding — not because it currently defeats the deadline.
|
||||
* - `connect_timeout` — no effect at all: `ConnectionParameters` recomputes it from
|
||||
* `connectionTimeoutMillis`, so `?connect_timeout=0` left it at the derived `10`. Rejected as
|
||||
* unsupported; earlier revisions of this comment wrongly claimed it overrode the explicit field.
|
||||
* - `options` carrying `statement_timeout` — measured **not** to override the named startup
|
||||
* parameter: `?options=-c statement_timeout=0`, `=900s`, and a backslash-escaped spelling all
|
||||
* still produced `10s`, as did an inherited `PGOPTIONS`. Rejected as unsupported rather than
|
||||
* because it wins.
|
||||
*
|
||||
* Deliberately narrow: unrelated parameters (`application_name`, and anything else that does not
|
||||
* name a timeout this boundary selects) are untouched. `PGCONNECT_TIMEOUT` needs no handling — the
|
||||
* driver consults it only when `connectionTimeoutMillis` is `undefined`, and this boundary always
|
||||
* sets it; an inherited `PGCONNECT_TIMEOUT=0` was measured to change nothing.
|
||||
*/
|
||||
const CONFLICTING_TIMEOUT_PARAMETERS = Object.freeze([
|
||||
"connect_timeout",
|
||||
"connectiontimeoutmillis",
|
||||
"query_timeout",
|
||||
"statement_timeout",
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user