You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while implementing #8696's mongodb arm, and filed rather than folded in: closing it inside that card would have required either a connect-time refusal (which #8696's ruling forbids — packages/spec declares injection on the URL-bearing arms) or a diagnostic seam the driver factory does not have. It belongs at the authoring door, which is a different package and a different lane. Deliberately unlabeled for triage.
What happens
After #8696, a mongo datasource that binds external.credentialsRef and authors config.url gets the secret injected as MongoClient's auth — but auth needs a username as well as a password, and the only place the username can come from is the URL's own userinfo. So the injection is conditional on the URL naming a user:
Injecting anyway is worse, not better. Measured on mongodb 7.5.0: mongodb://db.internal:27017/app carries no credentials at all, while the same url with auth{username:'', password:BOUND} carries credentials{username:''}. Fabricating an empty username converts a datasource that connects today into a guaranteed handshake failure.
That leaves the honest place: refuse the pair where both halves are visible at once, which is authoring/publish, not connect. This is the "absence must be loud" half of the family's inherited reason (#7314 / #7385 / #8152 / #8875 / #8696), the only part of it still unserved.
Where the refinement can see both halves.external.credentialsRef sits on the datasource, url sits in config, so this cannot be a MongoConfigSchema.superRefine — it needs the datasource-level schema (or the connection-form/publish validator that already sees both).
Direction of the message. Two authoring fixes are valid (add the username to the URL, or drop the binding), so the refusal should name both rather than prescribe one.
Backlink: #8696. Related: #8155 (the migration whose URL-bearing rows this class of row would otherwise reach), #8873 (postgres arm, must land before its equivalent is judged).
Found while implementing #8696's mongodb arm, and filed rather than folded in: closing it inside that card would have required either a connect-time refusal (which #8696's ruling forbids —
packages/specdeclares injection on the URL-bearing arms) or a diagnostic seam the driver factory does not have. It belongs at the authoring door, which is a different package and a different lane. Deliberately unlabeled for triage.What happens
After #8696, a mongo datasource that binds
external.credentialsRefand authorsconfig.urlgets the secret injected asMongoClient'sauth— butauthneeds a username as well as a password, and the only place the username can come from is the URL's own userinfo. So the injection is conditional on the URL naming a user:mongodb://app@db.internal:27017/app+ bound secret → injected, correct;mongodb://db.internal:27017/app+ bound secret → nothing happens. The datasource connects anonymously and the operator is told nothing.The second case is a configuration that cannot work as written, accepted in silence at every door it passes.
Why #8696 did not close it
Three constraints, all measured:
mongodb://db.internal:27017/appcarries no credentials at all, while the same url withauth{username:'', password:BOUND}carriescredentials{username:''}. Fabricating an empty username converts a datasource that connects today into a guaranteed handshake failure.MongoConfigSchema.urldeclares "bind the secret … and it is injected at connect time", and A boundexternal.credentialsRefis silently dropped on the DSN branches of the mysql and mongodb driver arms #8696's ruling is explicit that refusing would need adomain:specchange and would reject the shape Migration story for stored cleartext datasource credentials: operator-initiated re-homing into sys_secret (blocked by the managed-schemaMode refusal) #8155's landed migration instructs operators to write.const auth = user ? … : ''— no username, no credential, secret unused), so making only the DSN branch loud would plant a fresh per-branch asymmetry inside one function — the exact defect class A boundexternal.credentialsRefis silently dropped on the DSN branches of the mysql and mongodb driver arms #8696 closed.That leaves the honest place: refuse the pair where both halves are visible at once, which is authoring/publish, not connect. This is the "absence must be loud" half of the family's inherited reason (#7314 / #7385 / #8152 / #8875 / #8696), the only part of it still unserved.
Scope to settle before implementing
external.credentialsRefreaches the knex config but NOT the server on the postgres DSN branch —pgoverrides it with the connection string #8873) and should be re-read after that lands rather than assumed.external.credentialsRefsits on the datasource,urlsits inconfig, so this cannot be aMongoConfigSchema.superRefine— it needs the datasource-level schema (or the connection-form/publish validator that already sees both).urlUserinfoUsername(@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) deliberately distinguishesundefined(no userinfo) from''(present-but-empty). NoteMongoClientitself already refuses the''forms outright —mongodb://:p@h/dbandmongodb://@h/dbboth throwMongoParseError: URI contained empty userinfo section, measured — so only theundefinedcase is silent and only it needs this refusal.Backlink: #8696. Related: #8155 (the migration whose URL-bearing rows this class of row would otherwise reach), #8873 (postgres arm, must land before its equivalent is judged).