Only add @SECLEVEL=0 where the TLS backend supports it - #206
Conversation
|
|
|
Is there a specific reason you're looking to use Bun instead of just using Node here? Understanding the use case would help evaluate this. Mockttp closely integrates with lots of low-level engine functionality, especially the TLS implementation. E.g. https://github.com/httptoolkit/node-tls-impersonate is used here, and that's a native module that directly integrates with OpenSSL - it can't work with BoringSSL at all. Some of that functionality is optional, so you'll merely be unable to use some features, but continuing to test and fix this as both Bun & Node versions evolve in future adds significant complexity. I'm not sure it's worthwhile supporting this. Both this and httptoolkit/httpolyglot#4 seems like issues that should really be resolved in Bun's node-compat layer. That httpolyglot PR I'm more open to since it results in a single path that's roughly equivalent, but I'd really prefer to avoid a whole separate path and extra workarounds like this PR without a really good reason. |
@SECLEVEL is an OpenSSL extension to the cipher-string syntax. Backends that present an OpenSSL-compatible API without implementing it (BoringSSL) reject the entire cipher string with ERR_SSL_INVALID_COMMAND, so every passthrough request with relaxed certificate checks fails at tls.connect. Feature-detected rather than derived from process.versions.openssl, because that value isn't reliable: Bun reports '1.1.0', a real OpenSSL version that does support the directive.
0bdcd00 to
faf06b7
Compare
|
@pimterry Sorry for the httpolgylot one. That one was purely fixable in Bun, and I shouldn't have opened it. Closed it now. I was just probing the Bun's compatibility with this library, and this seemed like a low-hanging change. There was something wrong with my probe and I thought this change made it work. The mistake was that I was partially using Node. So this PR is also invalid now. Sorry for the trouble. Closing. |
getUpstreamTlsOptionsappends@SECLEVEL=0to the cipher string wheneverstrictHttpsChecksis off:@SECLEVELis an OpenSSL extension to the cipher-string syntax. TLS backends thatpresent an OpenSSL-compatible API without implementing it — BoringSSL, which is what
Bun uses — don't ignore the unknown directive; they reject the entire cipher string:
So on such a runtime every passthrough request with relaxed certificate checking fails
at
tls.connect, while strict requests are fine. That's an awkward failure mode,because relaxed checking is exactly what you reach for when recording against staging
hosts with self-signed certificates.
This adds
isSecLevelSupported()tosrc/util/openssl-compat.tsand gates thedirective on it. Where
@SECLEVEL=0is supported the cipher string is byte-identical totoday's; where it isn't, it's dropped, costing only tolerance for legacy/weak upstream
ciphers — which is strictly better than the connection failing outright.
Why feature detection rather than a version check
The obvious implementation is to read
process.versions.openssl, matching theareFFDHECurvesSupportedhelper next to it. That doesn't work here: Bun reportsprocess.versions.openssl === '1.1.0', a real OpenSSL version that does support@SECLEVEL. Any version-based check gets the wrong answer. So the helper does the onething that can't be spoofed — it builds a secure context with the directive once, and
caches whether that threw.
Worth flagging separately: the same reported-version problem means
NEW_CURVES_SUPPORTEDis
falseunder Bun for the wrong reason. It happens to be the right answer, so I'veleft it alone rather than widening this PR, but it's the same trap.
Tests
Extends
test/openssl-compat.spec.ts. The assertion is that the helper agrees with whattls.createSecureContextactually does on the host runtime, rather than a hardcodedexpectation — so it's meaningful on both an OpenSSL and a non-OpenSSL backend.
tsc --noEmittest/openssl-compat.spec.ts, Node 24.20.0test/openssl-compat.spec.ts, Bun 1.4.0I ran the unit spec rather than
npm test, which also builds and runs the browser andperformance suites; happy to run the full thing if you'd like it in the PR.
Notes
For context on where this came from: I was evaluating whether mockttp could run under
Bun as the interception layer for a project of mine. It can't yet, but for a reason
that's Bun's to fix and not yours — setting
SNICallbackthere suppresses ALPNentirely, so an h2-capable proxy silently downgrades every client to HTTP/1.1. Filed at
oven-sh/bun#41061.
This change and the companion httpolyglot one are the two things that are fixable
from the library side, and both are no-ops on Node. No expectation that you support Bun
as a target — I'd understand if you'd rather not carry either.