Uh oh!
There was an error while loading. Please reload this page.
feat(spec): refuse a postgres config.url that pg itself cannot parse at publish - #9158
Merged
Merged
Conversation
…at publish (#9091) PostgresConfigSchema.url documented a grammar it never enforced: the shared credentialFreeUrl/placeholderFree checks are string scans by design (their refusal to parse is load-bearing for mongo multi-host/+srv, #8696), so a URL pg throws on (libpq multi-host DSN, bad port) published green and failed at connect with a redacted Invalid URL. A per-driver superRefine now asks pg's own grammar (pg-connection-string parse, new spec dependency), refusing what parse throws on, scheme-less values parse only resolves against its placeholder base, and the fs-reading ?sslcert/?sslkey/?sslrootcert params (publish must not read the validating host's filesystem; certificates live in the datasource-level ssl block). ADR-0087 semantic entry + registry regen + docs regen; accept-side pins for every measured shape pg genuinely opens and for mongo's untouched multi-host form. Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code — list omitted above 15 rows. Re-derive: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.
|
os-zhuang
marked this pull request as ready for review
August 16, 2026 16:14
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9091
What
PostgresConfigSchema.urldocumented a grammar (postgresql://[user@][host][:port][/dbname][?params]) and enforced none of it: the value only went through the sharedcredentialFreeUrl/placeholderFreestring scans, whose refusal to parse is load-bearing for mongo's multi-host/+srvforms (#8696) — correctly lenient there, no check at all here. Measured onpg@8.22.0: bothpg-connection-string'sparseandpg'sConnectionParametersthrowTypeError [ERR_INVALID_URL]onpostgresql://app@h1:5432,h2:5433/app, yet the schema accepted it — the failure surfaced only at connect, as a bareInvalid URLwhoseinputfieldpgredacts.Per triage, the fix is a per-driver
superRefineon the postgresurlonly — the shared helper is untouched — that askspg's own grammar rather than re-modeling it:parsefrompg-connection-string@^2.14.0(the parserpgitself uses; added as a dependency of@objectstack/spec—service-datasourcealready depends on it for the #8873 runtime arm). Refused, each at the value's path:parsethrows on (multi-host DSNs, non-numeric ports, malformed percent-escapes), quoting the parser's own message and naming the multi-host cause plus the single-host/proxy replacement.parseonly "accepts" it by resolving against its placeholder base (postgres://base), sopgwould connect to the literal hostbasewith the authored text as the database name. Detection mirrors the parser's own preprocessing (space re-encoding, the@/empty-host retry), so the two cannot disagree about which branch ran.?sslcert=/?sslkey=/?sslrootcert=— these makeparseitself callfs.readFileSync, so letting them through would have publish-time validation reading the validating server's filesystem (verdict depends on which machine validates; existence oracle). Named explicitly as the one place this PR refuses somethingpgcan sometimes open: certificate material already has its declared home in the datasource-levelsslblock — the exact prescription the config-levelca/cert/keykeys carry (SSL_DETAIL_BELONGS_ON_DATASOURCE). Boundary mirrorspgexactly where it can: exact-case keys, non-empty values only (?SSLCERT=and?sslcert=stay accepted — measured,pgreads neither).Accept-side, pinned byte-identically: single-host URLs (credential-free included), empty-host libpq forms (
postgresql:///db,postgresql://user@/db), unix-socket spellings (leading-/path,socket:, percent-encoded host), IPv6 hosts, non-credential/non-fs query params — and mongo's multi-host form viaMongoConfigSchema(the shared helper's leniency it must keep). Composition pinned: a value violating both the #8082 credential rule and this one reports both issues.Contract change plumbing
packages/spec/src/migrations/entries/semantic/18.datasource-config-postgres-url-unparseable-refused.ts;registry.tsregenerated viapnpm --filter @objectstack/spec gen:migration-registry(never hand-edited).@objectstack/specminor (matching the sibling semantic-refusal changesets turso:?authToken=in an authoredconfig.url/config.syncUrlquery string is credential material the #8082 userinfo refusal does not cover #8337/mongoconfig.options.auth.passwordis a fourth spelling of an inline credential — authorable, persisted cleartext, unredacted, and read by the client #9040/Nothing refuses the contradictory pair "external.credentialsRefbound + aconfig.urlnaming no user" — the binding is a silent no-op at connect #9041's launch-window convention), body carries the FROM → TO migration andadr-0087: registeredmarker.content/docs/references/data/driver-postgres.mdxregenerated bycheck:generated --fix(describe/TSDoc change).Verification (all at 4f8f302, the final commit)
pnpm --filter @objectstack/spec test— 406 files / 10816 passed.pnpm --filter @objectstack/spec typecheck— green.pnpm --filter @objectstack/spec build && check:generated— all artifact gates green after--fixregenerated the one proved-stale artifact (docs).dist):@objectstack/service-datasource478 passed,@objectstack/runtime2468 passed. Fixture sweep over the rule's consumption radius found no repo fixture authoring a now-refused postgres URL.node scripts/pm/dispatch-gates.mjs <changed paths>) union run: cross-package-test-inputs, doc-formula-expressions (after building@objectstack/formula— fresh-worktree artifact, not this change), docs-audit-scope, docs-redirects, merge-driver, objectui-changeset, quick-reference-counts, release-page-status, role-word, spec-parsed-alias, type-source-resolution, adr-0087-registration, changeset-no-major, empty-changeset, dev-prereqs, query-options-erasure, engine-double-contract, where-matcher, type-check-coverage, type-check-debt--re-measure(workspace closure built first), check-nul-bytes — all green.Notes for review
parseon a URL with?sslmode=require|prefer|verify-caemitspg's own once-per-process deprecationprocess.emitWarning— pre-existingpgbehavior now reachable at publish-time validation; guarded by the library (typeof process !== 'undefined'), once per process.pg-connection-stringhas no runtime deps; itsfsrequire is lazy and unreachable from this call site (the fs-param refusal runs first). Worth knowing for browser bundles that may pull spec's data entry.external.credentialsRefbound + aconfig.urlnaming no user" — the binding is a silent no-op at connect #9041 / mongoconfig.options.auth.passwordis a fourth spelling of an inline credential — authorable, persisted cleartext, unredacted, and read by the client #9040's sibling refusals on the same file surface were re-verified untouched (their pins are in the 10816).Generated by Claude Code
Generated by Claude Code