Uh oh!
There was an error while loading. Please reload this page.
fix(service-datasource): a declared ssl reaches the mysql client on both branches, in the spelling mysql2 accepts (#8874) - #9126
Merged
Conversation
…he DSN branch (#8874) The mysql arm resolved the TLS option and returned before it could be used, so a datasource that declared TLS and wrote a config.url negotiated no TLS at all. Carry it beside the DSN — mysql2 reads a uri and the ssl option as separate channels — and only switch the bare-string return to an object when a declared ssl actually resolved. Also translates the resolved `true` into mysql2's spelling (`{}`): mysql2 throws `SSL profile must be an object` on a boolean, so the discrete-fields branch was failing every connection acquisition for the same declaration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
… and both DSN sub-cases (#8874) Asserts at mysql2's own ConnectionConfig, fed the exact connectionSettings knex hands it — the layer the sibling cards' reviews established, and the only one that can see the boolean half of this defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 1 changed package(s). ✅ |
os-project-manager
marked this pull request as ready for review
August 16, 2026 12:33
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#8874
A mysql datasource that declared TLS and wrote a
config.urlnegotiated no TLS at all.buildMysqlConnectionresolved the option and then returned before anything could use it — declared, resolved, dropped, with no diagnostic — while the discrete-fields branch of the same arm carried it. Whether a connection was encrypted therefore depended on which branch of one arm the datasource happened to take.The fork the card poses ("wire it, or refuse loudly") is taken as ruled at triage: honour the declared channel. There is nothing to refuse.
MysqlConfigSchemadeclares the key honoured with no branch caveat, itssslmode/tls/usesslaliases all rewrite to it, and where that schema means "not honoured, put it in the url" it says so in as many words — thecharsetguidance does exactly that, andssldoes not. The "TLS is a connection-string concern here" line the card half-remembers is the mongo arm's guidance, not this one's. And mechanically the branch can honour it:mysql2reads a uri and thessloption as separate channels, exactly aspgdoes.The card's cost estimate was wrong in a way that mattered, twice
Half one — the no-secret sub-case is not one line. With a secret bound, #8696 already made the branch return
{ uri, password }and addingsslthere really is one line. With no secret bound the branch returnedif (!spec.secret) return url;— a bare string, which has no key anssloption can live in. That is the return-shape change the card flagged as deserving its own decision, and it is where most of this diff's care went.Half two — the branch the card calls "honouring it" was throwing. Found while measuring half one. Measured on mysql2 3.23.1,
lib/connection_config.js, no connection opened:trueis exactly whatresolveSslOptionanswers for the two commonest declarations:ssl: { enabled: true }with no certificate material, and theconfig.sslshorthand, whose schema isz.boolean()and therefore has no other authorable value. So the discrete-fields branch has been handingmysql2a value that makes everyacquireRawConnectionthrow.pgtakes a boolean;mysql2takes an object or a bundled profile name.What was fixed here, and why the second half is not scope creep
Both halves, in one arm, because they are one defect wearing two spellings — and because emitting
ssl: trueonto the DSN branch to "honour" the declaration would have shipped the throw to a second branch. Delivering this card's own acceptance criterion (a declaredsslreaches the client) requires the translation; applying it to only one branch would have planted the exact per-branch asymmetry this card exists to remove.The translation is forced, not chosen:
resolveSslOption's own comment already states the equivalence — "ssl: {}would read as 'TLS with default options' … which is whatenabled: truewith nothing else means anyway" — somysqlSslOptionre-expands the collapsed form for the one client that cannot read it.rejectUnauthorized: trueon the result is mysql2's own default for an object (this.ssl.rejectUnauthorized = this.ssl.rejectUnauthorized !== false), not a verification policy invented here. Certificate objects,false, and a stored profile name ('Amazon RDS') all pass through untouched.No new dependency.
pg-connection-string, new in this package from #9090, is a postgres DSN parser and is deliberately not reached for —mysql2parses its own uri, so no second dialect ofmysql://…enters this repo.The blast radius is exactly the broken population
The DSN branch returns an object instead of the bare string only when a declared
sslactually resolved (mysqlSsl !== undefined) — or when a secret is bound, unchanged from #8696. A datasource that declared neither still gets the byte-identical string knex has always parsed for it. That scoping is asserted, not merely intended:leaves a DSN with NOTHING declared exactly as it wasandleaves a DSN with only a secret bound as the #8696 shapeare two of the six cases that stay green under ablation.Where the switch does happen the DSN moves from knex's own connection-string parser to mysql2's, so every non-TLS key it contributed has to arrive unchanged. Compared key-by-key on
host/port/user/password/database/charset/timezone/connectTimeout/flags/socketPath/multipleStatements, across the bare-username, embedded-password, no-userinfo, portless, percent-encoded-username and query-parameter (?charset=,?connectTimeout=,?timezone=) forms: identical in every case. Pinned as a test, not measured once.So the only datasources whose behaviour moves are the ones that were already broken — connecting in cleartext against their own metadata, or unable to connect at all.
A correction to an existing comment rides along, because this change depends on it: the #8696 paragraph describes mysql2's merge as filling in "keys the caller did not supply". It is actually
if (options[key]) continue;— falsy, not absent. Harmless for a bound secret (always truthy), load-bearing forssl, so it is now stated precisely.The stale comment the card asked about
The in-code comment declaring the omission deliberate and "filed separately" is gone, replaced by the section documenting how it was closed.
The pin asserts at the client-resolution layer
Inherited from #9042 and #8873 rather than re-learned: every assertion reads what mysql2 resolved —
ConnectionConfig, from the mysql2 module knex resolved for itself (knex.client.driver), fed the exactconnectionSettingsknex will hand it. Nothing asserts on theconnectionobject the factory built, except the two places where the return shape is itself the claim (a string cannot carry ansslkey), and their comments say so.Here that constraint is sharper than it was on the sibling cards: the boolean half is invisible at the config layer by construction —
{ ssl: true }is a perfectly good-looking object, and only the client's own parse says otherwise.new ConnectionConfig(settings)does no I/O, which is what makes the seam assertable without a server.Reverse verification — predicted in writing, then measured
Predicted before running, with the pre-fix branch restored and the tests at their fixed state: 8 failed / 6 passed, and specifically which. Measured exactly that set, case for case:
The last two are the sharper direction and the reason this file pins a branch the card describes as working: they go red by a different route from every other failure — the old branch did carry the option, as a value the client refuses. The six that stayed green are the ones that must not move: the two blast-radius bounds, the parser-equivalence sweep (it compares two parses, not the fix), and the three discrete-branch cases whose declaration was never the broken spelling.
The ablation was restored from the commit and proven byte-identical (
git hash-objecton the restored file equalsgit rev-parseof the same path at HEAD). Nothing is built for it: these tests import the factory source through the.js-to-.tstest alias, so the ablated code is the code that ran.Verification
All at
89c2e7ea, the final commit, tree clean.Gates — everything
node scripts/pm/dispatch-gates.mjsderived for the actual changed paths (seven path-matched, five convention-triggered by adding a test file), pluscheck:nul-bytes. All green:Not run locally:
check:type-check-debt --re-measure. It needs the whole workspace closure built and re-runstscper ledger entry —service-datasourcecarries no entry (it declares a realtypecheckscript and passes it, green above), so nothing in this diff can move that ratchet. Its structural half is green above. CI runs it either way.Out of scope, filed not fixed
MysqlConfigSchema.sslsays "passed to mysql2 verbatim" — the arm now translates the boolean, so the doc line is stale #9125 —MysqlConfigSchema.ssl's doc line still says "passed tomysql2verbatim", which this change makes stale for the boolean. It lands inpackages/spec, a different verification surface (authorable-surface anchor, JSON-Schema projection), so it is a separate card rather than a rider. Nothing in this diff touchespackages/spec.Generated by Claude Code