Uh oh!
There was an error while loading. Please reload this page.
feat(spec): refuse bound external.credentialsRef with a user-less mongo config.url at publish (#9041) - #9146
Merged
os-zhuang merged 2 commits intoAug 16, 2026
Conversation
…go config.url at publish (#9041) The binding is a silent no-op at connect (MongoClient auth needs a username the URL must supply; injecting a fabricated empty one is a measured handshake failure), so the contradictory pair is refused at the datasource level — the one door that sees both halves. Fences per triage: mongodb arm only (legacy 'mongo' alias-resolved), urlUserinfoUsername undefined only, message names both fixes prescribing neither. ADR-0087 semantic entry + registry regen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq
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.
|
…dentialsref-url-no-user-refusal
os-zhuang
marked this pull request as ready for review
August 16, 2026 14:52
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang
deleted the
claude/issue-9041-credentialsref-url-no-user-refusal
branch
August 16, 2026 15:09
This was referenced Aug 16, 2026
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#9041
What
Refuses, at the authoring/publish door, the contradictory pair "
external.credentialsRefbound + a mongoconfig.urlwhose userinfo names NO user" — a configuration that cannot work as written. Premise re-verified on the post-#9123 tree:buildMongoAuthin the driver factory injects the bound secret only whenurlUserinfoUsername(url)names a user (MongoClientauthneeds a username the URL must supply), so on a user-less URL the binding is a silent no-op — the datasource connects anonymously and the operator is told nothing. The factory's own #8696 comment defers the loud half to this door by name.Where — the seam that sees both halves
DatasourceSchema's existingsuperRefineinpackages/spec/src/data/datasource.zod.ts— the card measured this cannot be aMongoConfigSchema.superRefine(credentialsRefsits on the datasource,urlinsideconfig), and the datasource-level refinement is the one place inpackages/specboth halves are visible at once (no fork: the PM's assumed seam exists on today's tree).Fences (triage, adopted verbatim)
resolveDriverId, so a legacydriver: 'mongo'row is judged identically to'mongodb'(the same alias mechanism the mongoconfig.options.auth.passwordis a fourth spelling of an inline credential — authorable, persisted cleartext, unredacted, and read by the client #9040 read-path redaction uses). The postgres arm is not assumed: A boundexternal.credentialsRefreaches the knex config but NOT the server on the postgres DSN branch —pgoverrides it with the connection string #8873 measuredpginjecting on a user-less DSN by its own mechanism, and a fence-② pin asserts a user-less pg DSN + binding stays accepted.urlUserinfoUsernameanswersundefinedonly — the present-but-empty forms (mongodb://@h/db,mongodb://:p@h/db) already throw in MongoClient itself (MongoParseError: URI contained empty userinfo section); pinned as NOT this refusal.credentialsRefis not a binding (the connect path resolves under a truthy check); the composed branch (nourl) is untouched — its discreteusernamefield is live; a non-stringurlis left to the driver-config gate.Composition (PM-mechanism pins)
credentialFreeMongoOptionsreports both, each at its own path (config.url+config.options.auth.password) — pinned.user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 direction composes by construction: auser:password@URL HAS a user, so the author gets exactly 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 prescription and never a contradictory second message — pinned.Envelope
Per the sibling refusals' documented pattern (#8082/#9040 pins in the same test file): the zod issue's
code: 'custom'+ pathed location are the whole envelope at this layer; the publish door wraps every schema refusal uniformly as422 INVALID_METADATAwith these issues carried verbatim.Breaking + ADR-0087
Accept-set narrowing shipped as
@objectstack/specminor with BREAKING annotation, matching the #8337/#9123 sibling convention (a stored row carrying a vestigial binding over a working anonymous connection now refuses — a real accept-set flip, not only dead configs). Semantic migration entrydatasource-credentialsref-mongo-url-no-user-refusedregistered under major 18 (registry regenerated viagen:migration-registry); no mechanical rewrite is possible because the two fixes are contradictory intents (authenticate vs anonymous).Consumer fixture sweep
Swept every file binding
credentialsRefacross packages/examples/content/skills: no fixture pairs it with a user-less mongo URL (service-datasource'sbound-secret-dsn-branches.test.tsbuilds connection specs directly, below this schema; the #8155 migration keeps the username in the URL by prescription; the showcase example'scredentialsRefis commented out and postgres). Zero flips expected, and the full spec suite confirms.Verification (all at
d4fbe7352, the head commit)@objectstack/specsuite: 406 files / 10797 tests passed (includes the new describe block, 13 new cases).git checkoutfrom the branch), the refusal test file went 4 failed / 129 passed — exactly the four refusal-asserting cases; accept pins stayed green. Restored and re-ran: 181/181 green. Vitest runs fromsrc, so no dist rebuild was involved in the ablation loop (not a dogfooddist/ablation).pnpm --filter @objectstack/spec check:generated: all 13 artifacts up to date after rebuild (an earlier api-surface "stale" was the stale-dist phantom; rebuilt, clean, zero artifact diffs beyond the regenerated migration registry).pnpm --filter @objectstack/spec typecheckgreen;check:type-check-coverage+check:type-check-debt --re-measuregreen (no surplus).node scripts/pm/dispatch-gates.mjsover the 5 changed paths), all green:check:changeset-gate-self-tests,check:cross-package-test-inputs(both spellings),check:doc-formula-expressions(after building the lint closure),check:merge-driver,check:objectui-changeset,check:spec-parsed-alias,check:type-source-resolution,check-adr-0087-registration(BREAKING changeset carriesregistered datasource-credentialsref-mongo-url-no-user-refused),check-changeset-no-major,check-empty-changeset,check-dev-prereqs(after full package build), plus the test-file convention gatescheck:query-options-erasure,check:engine-double-contract,check:where-matcherandcheck:nul-bytes.validate(crm / showcase / todo): green (pre-existing warnings only).Generated by Claude Code
Generated by Claude Code