Repository navigation
fix(security)!: replace trustSelfSignedCertificates with ServerTrust (CWE-295) - #3
Merged
Merged
Conversation
… nothing `trustSelfSignedCertificates: true` set NIOSSL's `certificateVerification` to `.none` in all three network transports. That does not trust a self-signed certificate; it accepts any certificate from anyone able to answer the connection (CWE-295). `ServerTrust` replaces it, one type for all three transports: `.system`, `.additionalRoots(_:)`, `.onlyRoots(_:)`. The caller supplies the certificate (PEM text, DER bytes, or a file of either) and it becomes a trust root; chain and hostname verification stay on in every case, and no code path writes `certificateVerification` at all. Sources are read and parsed when the value is made, so an unreadable certificate is an error where the configuration is written and nothing falls back to the system roots. The old parameter is kept only as an `unavailable` initializer on each transport, so a caller gets a compile error that says what to write instead of a build that compiles and then behaves differently. Supplied roots are enforced by NIOSSL on every platform. On Apple platforms AsyncHTTPClient otherwise runs on Network.framework and translates the TLSConfiguration for it, and that translation drops `additionalTrustRoots` entirely; a client with supplied roots now runs on NIO's own event loops so the configuration that is asserted is the one that is enforced. No hash pin: neither AsyncHTTPClient nor WebSocketKit exposes NIOSSL's verification callback, and a pin only some transports could enforce is worse than none. `onlyRoots` with the server's own certificate is the pin. MCPExplorer's "Trust self-signed certificates" toggle becomes a path to a certificate file, and now applies to WebSocket too. Tests handshake against a TLS server on loopback with certificates minted at run time: the right root connects; a different self-signed certificate, a leaf from another CA, and a trusted certificate for another host are each refused with zero requests reaching the server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114bFC1osyZq6nFD4QrXDD2
…ificates CHANGELOG gains 0.13.0: what the old flag actually did, why it is removed rather than deprecated, the before/after, and the two checks a development server now has to pass (a SAN naming its host, a chain ending at a supplied root) with the openssl commands to produce a certificate that does. It also gains link-reference definitions for every version, which it never had. README replaces the option in its example and adds a section on self-signed and private-CA servers; the "Linux CI outstanding" line is corrected, since CI has been green since 0.11.0. DocC: TransportGuide gains "Self-Signed and Private-CA Servers", the MigrationGuide gains the 0.13.0 step, and the catalogue's Transport topic lists ServerTrust, ServerTrustError and the StreamableHTTPTransport it had been missing. The ServerTrust doc-comment examples are self-contained so the gate can compile them. HTTPSSETransport no longer claims TLS behaves identically on macOS and Linux: that is true only for a transport given roots, and the comment now says so. 0.13.0 is written up but not tagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114bFC1osyZq6nFD4QrXDD2
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 free
to 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.
What was wrong
HTTPSSETransport,StreamableHTTPTransportandWebSocketTransporteach tooktrustSelfSignedCertificates: Bool. Passingtrueset NIOSSL'scertificateVerificationto.none. That does not trust a self-signed certificate — it stops checking certificates, so the connection accepts anything presented by anyone able to answer it (CWE-295). The quality gate's newsecurity.tls-disabledrule flags all three sites as errors.What replaces it
One public type,
ServerTrust, taken by all three transports asserverTrust:(default.system):ServerTrust.systemtry ServerTrust.additionalRoots([...])try ServerTrust.onlyRoots([...])Sources are
.pem(String),.der([UInt8]),.pemFile(String),.derFile(String). They are read and parsed when the value is made; a bad path, unparseable text, or an empty list throwsServerTrustErrorthere, and nothing falls back to the system roots.rootFingerprintsexposes the SHA-256 of each supplied root.Chain and hostname verification stay on in every mode.
ServerTrust.makeTLSConfiguration()is the only place a clientTLSConfigurationis built, and it never writescertificateVerification.Decisions worth reviewing
@available(*, unavailable)initializer, so the old spelling is a compile error whose message gives the replacement.onlyRootswith the server's certificate is the pin all three can honour.TLSConfiguration; that translation ignoresadditionalTrustRootsentirely. With supplied roots the client is built onMultiThreadedEventLoopGroup.singletonso NIOSSL enforces what the tests assert..systemkeeps the platform stack — no change for callers who pass nothing.opensslcommands.MCPExplorer's "Trust self-signed certificates" toggle becomes a "Trusted certificate file" field, and now applies to WebSocket too.
Migration
Where the argument was
false, delete it. Where it wastrue:No consumer in the sibling repositories passes the old argument (geo-audit constructs its transports without it), so nothing downstream breaks.
Tests
Written failing first. 26 new, 3 vacuous ones (
#expect(Bool(true))after constructing with the flag) deleted.ServerTrustTests(15) — the NIOSSL configuration per mode:.fullVerification, exacttrustRoots/additionalTrustRoots; fingerprints checked against an independent SHA-256; every failure mode; and the configuration each of the three transports actually uses.ServerTrustWireTests(8) — an HTTPS stub on loopback with certificates minted at run time (no key committed). The right root connects; a different self-signed certificate, a leaf from another CA, a trusted certificate for another host, and the default against a self-signed server are each refused with a TLS error and zero requests reaching the server.TrustedCertificateFileTests(3) — MCPExplorer.Verification
swift test: 586 tests passed (578 + 8).quality-gate --check all: PASSED, 46 of 46 checkers, 0 errors / 0 warnings.--check safety --no-cacheon a scratch copy: 3security.tls-disablederrors onmain, none on this branch; no othersecurity.*findings.Not tagged;
0.13.0is written up in the changelog only.🤖 Generated with Claude Code
https://claude.ai/code/session_0114bFC1osyZq6nFD4QrXDD2