Uh oh!
There was an error while loading. Please reload this page.
feat(spec): refuse credential-bearing URL query parameters (?authToken= / ?password=) in authored driver config at publish (#8337) - #8925
Conversation
…password=) in authored driver config (#8337) Write door: shared value-level parse urlCredentialQueryParams beside #8082's urlUserinfoPassword; turso url/syncUrl ?authToken= and postgres url ?password= refused with the binder/credentialsRef prescription. Read door: redactUrlCredentials strips the pair from served URLs for every driver; restoreRedactedConfig mirrors the composite; the migration planner refuses query-token rows. ADR-0087 semantic entry under protocol major 18. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
…21-ism and a field-name in tests (#8337) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
os-regen-merge.sh steps 1-3 took main's side of the generated artifacts and committed the merge; this commit discharges the deferral: gen:migration-registry re-concatenates both protocol-18 registrations (92 semantic — the sibling's datasource-config-url-query-credential-refused and this branch's record-chatter-position-vocabulary-converged), spec rebuilt, projections re-run. Both sides asserted present: this branch's conversion + semantic ids, the sibling's entry and its urlCredentialQueryParams implementation (grep count matches origin/main). check:generated all 13 current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
…derer's vocabulary; the row's schema defaults are dropped (objectstack-ai#8762) (objectstack-ai#8914) * fix(spec): record:chatter/record:discussion position speaks the renderer's vocabulary; schema defaults dropped (objectstack-ai#8762) The shared chatter/discussion row declared position: sidebar|inline|drawer — a vocabulary no renderer read point ever compared (RecordChatterPanel branches on right/left vs bottom, measured at objectui pin 665661ab0932), with a .default('sidebar') that materialized the silent no-op onto every parsed node and a collapsible default (true) that inverted the renderer merge's false. Maintainer ruling 2026-08-15 (issue comment 5299771841, four binding points): converge on the renderer's bottom/right/left with no mapping layer; ADR-0087 conversion for the old spellings (sidebar→right, inline→bottom, drawer→right; protocol 18, retired from the load path — the enum refuses live authors with a per-value prescription keyed on issue.input, the objectstack-ai#8010 pdf precedent); drop all three schema defaults per the maxVisible principle; keep the pair on ONE shared schema object (objectstack-ai#8744). Adds the default-change ledger entries (check:authorable-surface), the protocol-18 semantic entry, and the step-18 conversionIds/rationale update. Tests re-triaged: default pins replaced with no-default pins, old-vocabulary fixtures re-spelled, per-value rejection pins added on both names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt * docs(spec): regenerate ui/component reference for the chatter row (objectstack-ai#8762) gen:spec-changes / gen:upgrade-guide / gen:api-surface re-run and byte-identical — the projections exclude protocol-18 steps until the major cut (the post-cut precedents' expected shape); check:generated proves all 13 artifacts current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt * merge origin/main (os-regen artifacts taken from main; regeneration follows) * chore(spec): regenerate after the objectstack-ai#8925 relay merge (objectstack-ai#8762) os-regen-merge.sh steps 1-3 took main's side of the generated artifacts and committed the merge; this commit discharges the deferral: gen:migration-registry re-concatenates both protocol-18 registrations (92 semantic — the sibling's datasource-config-url-query-credential-refused and this branch's record-chatter-position-vocabulary-converged), spec rebuilt, projections re-run. Both sides asserted present: this branch's conversion + semantic ids, the sibling's entry and its urlCredentialQueryParams implementation (grep count matches origin/main). check:generated all 13 current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes#8337
packages/spec/src/migrations/registry.tsis touched (generated,merge=os-regen, NOT_DRIVER_MANAGED): a new ADR-0087 semantic entry18.datasource-config-url-query-credential-refused.tswas added and the registry regenerated withgen:migration-registry(91 semantic entries). Landing runs the os-regen relay; the entry file is the hand-written source, the registry hunk is pure regeneration.What this closes
The third spelling of the same secret. #7990 refused the inline credential keys, #8082 refused the URL userinfo form (
user:password@host); the query string stayed open:libsql://x.turso.io?authToken=eyJ...persisted the JWT cleartext intosys_metadata(served back by the ordinary data API), and the #8126 read path stripped only userinfo, sogetDatasource()served the token back verbatim. Per the maintainer re-route (「8337 同意转 spec 你帮我转」, comment 5302062384) the fix is the shared value-level parse inpackages/spec— not a driver-local check.Measurements (all static reads of the client sources pinned in this tree)
?password=/?authToken=in the URL query@libsql/core@0.17.4expandConfigreads?authToken=and assigns it over the config-level token — it does not merely authenticate, it silently overrides the binder-injected secret at connect. Keys percent-decoded, exact-case; unknown params throwURL_PARAM_NOT_SUPPORTED.authTokenpg-connection-string@2.14.0config.password = config.password || url password—?password=honoured, wins over userinfopasswordmysql2parseUrlseedspasswordfrom userinfo, thenif (key in options) continue;over the query —?password=never reaches the connectionmongodb@7passwordis not a URI optionThe mysql/mongo absences are the dispatch's own boundary (「do not speculatively refuse generic params no client reads」) and are pinned by acceptance tests naming the measurement. Note this static read of
@libsql/coreis strong evidence for, but not a substitute for, #8860's end-to-end connect measurement — that card stays with the drivers lane and does not gate this one; the at-rest half justifies the refusal alone.Write door (
domain:spec)common.zod.ts:CREDENTIAL_URL_QUERY_PARAMS(per-driver, measured),URL_CREDENTIAL_QUERY_PARAM_REFUSED(key, param),credentialQueryParamOf+urlCredentialQueryParams(hand-parsed per RFC 3986 boundaries — first?to#, pairs split on&, key before first=, percent ++decoded — nevernew URL(), for [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082's fail-open reason).credentialFreeUrlgains an optional third argument; keys without declared params are judged exactly as before.url+syncUrl(authToken) and postgresurl(password).user:@host, which [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 accepts; it is also not what the redactor emits (the redactor drops the pair whole).user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 template (binder /external.credentialsRef, no-placeholder warning, runtime-DSN carve-out) but deliberately does not say the bound secret "wins over anything embedded in the URL" — for the turso query form that sentence would be false (the URL token wins, measured). The tests pin its absence.code+ re-pathedconfig.urllocation + message shape (statusdoes not exist at the schema layer; the publish door wraps every schema refusal uniformly as422 INVALID_METADATA— same note as the [Decision] URL-embedded credentials (user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 pins in this file).Read door (same PR, per the card)
datasource-credential-redaction.ts: newredactUrlCredentialQueryParams(strips the pair whole — dropped, not masked, same reasoning as the inline keys; preserves other params, order, fragment byte-for-byte) andredactUrlCredentials(userinfo + query composite).redactDatasourceConfignow uses the composite. Pair judgement is delegated to the write door'scredentialQueryParamOf, so the two doors share ONE boundary definition instead of the comment-level alignment the userinfo pair needed. Redaction is name-based and driver-independent (a served?password=is a leak under any boundary — the module's documented unknown-driver posture), so mysql/mongo URLs are stripped on read even though their write door does not refuse: the write door judges what a measured client reads, the read door what a caller may see.service-datasource:restoreRedactedConfigmirrors the composite (an untouched "Save" on a legacy?authToken=row keeps the stored token — restore runs afterassertValidConfig, which judges only the author's redacted patch, same as [services half of #7990] Close the datasource/connector credential write/read paths: scrubgetDatasource().config, fix the false "credential-stripped" claim, and write the stored-cleartext-rows migration story #8081);urlCredentialKeysin the credential-migration planner now detects the query spelling, so a stored token row is refused with the per-row remedy instead of planningnothing-to-migrateover cleartext; re-exports extended.Blast radius / consumer sweep
Consumers enumerated by grep over the redaction seam (
redactUrlPassword/redactDatasourceConfig/redactableConfigKeys/credentialFreeUrl): spec kernel metadata-type-redaction hook, service-datasource (admin service, migration planner, driver factory comment), CLIconnection-display(separate display-time redactor for runtime env DSNs — never passes through these schemas, already strips query tokens itself, untouched). Fixture sweep forauthToken=/libsql:///?password=acrosspackages/,examples/,content/: no fixture publishes a credential-bearing query URL through the narrowed schemas (the CLI fixtures exercise the display redactor, not the authoring door), so no fixture re-spelling was needed. Consumer-direction note: the packages tested below are downstream consumers of@objectstack/spec(prefix filter direction), tested against the rebuilt specdistvia their built workspace deps.Verification (union re-run at final commit; sha quoted in the report)
pnpm --filter @objectstack/spec test: 402 files / 10687 tests passedpnpm --filter @objectstack/service-datasource test: 19 files / 432 tests passedpnpm --filter @objectstack/driver-turso test: 994 tests passed (fixture triage: no fixtures carried the narrowed spelling)pnpm --filter @objectstack/spec typecheck+pnpm --filter @objectstack/service-datasource typecheck: greencheck:generatedgreen aftergen:migration-registry+ rebuild +gen:api-surface+gen:export-origins(api-surface diff is additive-only: 5 new exports)turso.zod.tsrestored toorigin/main(wiring removed, shared parse kept), exactly 9 tests failed — the 4 refusal pins x tursourl/syncUrlrows + the DatasourceSchema-level pin; the turso placeholder/acceptance pins and the postgres row correctly stayed green. Restored from the committed branch and re-run to full green.node scripts/pm/dispatch-gates.mjs— it addedcheck:changeset-gate-self-tests,check:objectui-changeset,check:changeset-no-major,check:empty-changesetbeyond the dispatch list; all green (see report for the full list).Bump class
@objectstack/spec: minorwith a BREAKING body — the post-GA lockstep launch-window convention (per.changeset/analytics-authorable-unknown-keys-refused.mdand siblings, and mechanically enforced bycheck-changeset-no-major), not #8341's pre-cutmajor: same accept-set-narrowing class, different release window. Migration prescription registered under protocol major 18.@objectstack/service-datasource: patchfor the read-path behaviour. ADR-0087 disposition:registered datasource-config-url-query-credential-refused.Out-of-scope finding filed: none needed beyond what the thread already carved out (#8860, drivers lane, pre-existing).
Generated by Claude Code
Generated by Claude Code