Skip to content

Only add @SECLEVEL=0 where the TLS backend supports it - #206

Closed
gkurt wants to merge 1 commit into
httptoolkit:mainfrom
gkurt:seclevel-feature-detect
Closed

Only add @SECLEVEL=0 where the TLS backend supports it#206
gkurt wants to merge 1 commit into
httptoolkit:mainfrom
gkurt:seclevel-feature-detect

Conversation

@gkurt

@gkurt gkurt commented Aug 31, 2026

Copy link
Copy Markdown

getUpstreamTlsOptions appends @SECLEVEL=0 to the cipher string whenever
strictHttpsChecks is off:

...(!strictHttpsChecks ? ['@SECLEVEL=0'] : [])

@SECLEVEL is an OpenSSL extension to the cipher-string syntax. TLS backends that
present 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:

error:0A0000B9:SSL routines:OPENSSL_internal:INVALID_COMMAND

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() to src/util/openssl-compat.ts and gates the
directive on it. Where @SECLEVEL=0 is supported the cipher string is byte-identical to
today'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 the
areFFDHECurvesSupported helper next to it. That doesn't work here: Bun reports
process.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 one
thing 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_SUPPORTED
is false under Bun for the wrong reason. It happens to be the right answer, so I've
left 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 what
tls.createSecureContext actually does on the host runtime, rather than a hardcoded
expectation — so it's meaningful on both an OpenSSL and a non-OpenSSL backend.

result
tsc --noEmit clean
test/openssl-compat.spec.ts, Node 24.20.0 6 passing
test/openssl-compat.spec.ts, Bun 1.4.0 6 passing

I ran the unit spec rather than npm test, which also builds and runs the browser and
performance 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 SNICallback there suppresses ALPN
entirely, 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.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@pimterry

Copy link
Copy Markdown
Member

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.
@gkurt
gkurt force-pushed the seclevel-feature-detect branch from 0bdcd00 to faf06b7 Compare August 31, 2026 16:17
@gkurt
gkurt marked this pull request as draft August 31, 2026 16:32
@gkurt

gkurt commented Aug 31, 2026

Copy link
Copy Markdown
Author

@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.

@gkurt gkurt closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants