Uh oh!
There was an error while loading. Please reload this page.
feat(spec): refuse a credential in the mongo options passthrough (config.options.auth.password) at publish (#9040) - #9123
Merged
Conversation
…fig.options.auth.password) at publish (#9040) Write door: closed measured list (MONGO_OPTIONS_CREDENTIAL_PATHS) behind credentialFreeMongoOptions, composed with placeholderFreeDeep on MongoConfigSchema.options; non-empty string auth.password refused with the binder prescription (bound secret measured outranking the passthrough at connect, #8696). Read door: passthrough secret paths (auth.password, proxyPassword, TLS key material, AWS_SESSION_TOKEN) redacted with dotted redactedKeys; restoreRedactedConfig mirrors per leaf; the credential-migration planner refuses stored passthrough-credential rows with the per-row remedy. ADR-0087 semantic entry (registry regen to follow). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq
…ns, references; add changeset (#9040) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq
Contributor
📓 Docs Drift CheckThis PR changes 2 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 12:42
Uh oh!
There was an error while loading. Please reload this page.
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#9040
Route ① — refuse at publish (the inherited #8082/#8337 direction)
config.options.auth.passwordwas the FOURTH spelling of the inline credential (#7990 top-level key, #8082 URL userinfo, #8337 URL query params). Measured onorigin/main@479ec31c9before editing (premise re-probed, still valid): the config parsed green, the password persisted cleartext,redactDatasourceConfig('mongodb', …)served it back withredactedKeys: []— and mongodb@7.5.0 transforms the block intoMongoCredentials, so it genuinely authenticated.The refusal set is a CLOSED measured list, not a heuristic scan (
MONGO_OPTIONS_CREDENTIAL_PATHSindriver/common.zod.ts): a non-empty stringoptions.auth.password. Why each candidate is in or out — every claim measured against mongodb@7.5.0, the client@objectstack/driver-mongodbspreadsconfig.optionsinto:auth.password— REFUSED.OPTIONS.authresolves it intoMongoCredentials(measured:c.options.credentials.passwordcarries it verbatim); the binder remedy genuinely works and wins at connect (A boundexternal.credentialsRefis silently dropped on the DSN branches of the mysql and mongodb driver arms #8696's pin). Issue pathed atoptions.auth.password,code: 'custom', message naming the secret binder /sys_secret/external.credentialsRef— re-pathed underconfig.options.auth.passwordon the authored artefact (the publish door wraps every schema refusal as 422 INVALID_METADATA per the family's documented envelope;statusdoes not exist at the schema layer — the sibling [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/turso:?authToken=in an authoredconfig.url/config.syncUrlquery string is credential material the #8082 userinfo refusal does not cover #8337 pins document the same).auth.usernamealone — NOT refused (dispatch fence,@objectstack/spec/dataowns the userinfo grammar but exports only its password half — the mongo DSN arm cannot inject a bound credential without a username accessor #8876 asymmetry). Pinned accepted. (The client itself throws on a username-onlyauthblock — its own loud contract, not this door's.)auth.password— NOT refused (the passthrough twin ofuser:@host, [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). Pinned.authMechanismProperties.AWS_SESSION_TOKEN— NOT refused: underauthMechanism: 'MONGODB-AWS'the v7 client itself throws on it (MongoAPIError, measured); under any other mechanism nothing reads it. Refusing would be turso:?authToken=in an authoredconfig.url/config.syncUrlquery string is credential material the #8082 userinfo refusal does not cover #8337's "speculative widening". Redacted on read instead.proxyPassword,tlsCertificateKeyFilePassword,key,passphrase— NOT refused: honoured client secrets (SOCKS5 auth, TLS key material) with NO binder slot — the binder injects exactly one secret (the login password), so a refusal would point at a remedy that cannot work and remove the only way to configure an authenticated proxy / passphrase-protected key. Turso-encryptionKeyposture ([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 item 4). Redacted on read; the open at-rest half is filed as a finding (see below).replicaSet,tls, timeouts) — byte-identical pins, including the redacted round-trip shape (authwith only a username), which keeps an untouched "Save" on a legacy row working.False-positive analysis for a generic deep key-name walk (the alternative the card allowed if strictly safer): it is not safer here — the client's OPTIONS table is a closed set, only the paths above resolve into secrets, and a name-walk would judge author-typed junk the client never reads while adding nothing over the measured list. Closed list chosen.
Composition with
placeholderFreeDeep(PM-mechanism assumption, verified): both checks are independentsuperRefines on the same slot; a${…}password reports BOTH issues atoptions.auth.password(pinned: exactly 2 issues, one naming #9040, one #8336). The wrapper's own semantics are unchanged.Route ② — redaction, additive (measured cheap because the carry-forward machinery already generalizes)
redactDatasourceConfignow drops the measured passthrough secret paths (tablePASSTHROUGH_SECRET_PATHS, mongo-only, alias-resolved so legacydriver: 'mongo'rows scrub identically), reporting dottedredactedKeys(options.auth.password) — the exact shape the metadata write door's genericcarryForwardRedactedValuesalready walks (probed: nested round-trip preserves the stored credential; a deleted container is never re-grafted; zero changes in metadata-protocol).restoreRedactedConfig(service-datasource) mirrors per leaf with the module's own narrow rule: restore only when the leaf's container exists in the patch and the patch does not speak to the leaf. Pinned: untouched Save keeps the credential; sibling-option edits keep it; deleting theauthblock wins; a typed-in password wins and is refused by the write gate on its own merits; no aliasing back into the caller's patch object.authblock the client refuses at construction, measured) — the same arm shape turso:?authToken=in an authoredconfig.url/config.syncUrlquery string is credential material the #8082 userinfo refusal does not cover #8337 added for query-token rows.Scope measurement (the card's question): is the door mongo-only?
Yes — measured, not assumed. postgres/mysql/turso/sqlite/sqlite-wasm/memory ship closed
strictObjectconfig contracts with no client-bound record slot; memory'sinitialDatais seed DATA (deliberately unjudged — redacting a seeded row's ownpasswordfield would corrupt served data). Pinned in the redaction suite (passthroughSecretPaths(driver)empty for every non-mongo builtin). No equally-open sibling door exists, so no shared rule is warranted; the helper still lives indriver/common.zod.tsbesidecredentialFreeUrlso a future passthrough joins the same mechanism.ADR-0087 / changeset
Semantic entry
18.datasource-config-mongo-options-credential-refused(registry regenerated viagen:migration-registry); changesetminorfor spec with BREAKING annotation +patchfor service-datasource, following #8337's convention. No automatic rewrite, same reason as the three siblings (a source transform cannot encrypt intosys_secret; auto-dropping the leaf alone would brick theauthblock).Verification (union run at final commit
e3b754e23)pnpm --filter @objectstack/spec test— 406 files / 10777 passed.pnpm --filter @objectstack/service-datasource test— 453 passed (includes A boundexternal.credentialsRefis silently dropped on the DSN branches of the mysql and mongodb driver arms #8696'sbound-secret-dsn-branchesprecedence pin, untouched).pnpm --filter @objectstack/metadata-protocol test— 112 files / 1565 passed.pnpm --filter @objectstack/driver-mongodb test— 385 passed / 143 skipped (mongod-gated). Typecheck green on spec + service-datasource; examplesvalidategreen.mongo.zod.tsrestored to origin/main ⇒ exactly the 3 write-door pins red (refusal, placeholder-composition, artefact re-path), 143 others green ⇒ restored from the committed branch, suite green again.dist/: the issue's own snippet now answerssuccess: falseatoptions.auth.password, and redaction servesauth: { username }withredactedKeys: ['options.auth.password'].scripts/pm/dispatch-gates.mjsagainst the actual diff — beyond the dispatch-named set it addeddocs-audit-scope,docs-redirects,objectui-changeset,quick-reference-counts,role-word,test-source-alias,adr-0087-registration,changeset-no-major,empty-changeset; ALL green, pluscross-package-test-inputs,doc-formula-expressions,merge-driver,spec-parsed-alias,type-source-resolution,dev-prereqs(green after full closure build — first red was stale-worktree, 45 unbuilt packages),nul-bytes,query-options-erasure,engine-double-contract,where-matcher,type-check-coverage,type-check-debt --re-measure(33 entries, none above measurement), andcheck:generated— all 13 artifacts up to date (api-surface, export-origins, references regenerated).Generated by Claude Code