Repository navigation
Provide APIs to help control TLS fingerprints #41112
Description
Is your feature request related to a problem? Please describe.
There are servers in the wild online (at least all sites using Akamai's CDN bot management feature) which actively block all connections from Node.js clients by examining their TLS fingerprint (more details).
There are some limited options to work around this today, such as reordering cipher suites, but they have security consequences which make this hard to do safely, and which limit the set of valid configurations.
While reordering ciphers has security consequences, reordering the extensions in the client hello is a semantically meaningless & safe change that would make it possible to completely defeat TLS fingerprinting.
Unfortunately, there are no APIs exposed that would allow Node.js developers to do this today.
Describe the solution you'd like
An API to configure the order that TLS extensions are set in the client hello would be perfect. An API or command line option which simply randomized the order for each connection would also be very good (equally effective for this use case I think, but a bit less flexible for advanced tricks, like emulating another TLS client's extension order).
Randomizing once at process startup might potentially be good, perhaps via a command line option, but that creates new per-process fingerprinting opportunities that could be problematic.
Describe alternatives you've considered
Currently the only alternative is changing the list of order of ciphers, which does work to defeat fingerprinting in the short term, but provides limited scope and requires detailed TLS knowledge to do safely.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsAwaiting Triage
Activity
pimterry commentedon Dec 7, 2021
(Edit: link was wrong, now fixed, correct URL is https://httptoolkit.tech/blog/tls-fingerprinting-node-js/)
NichyX commentedon Feb 6, 2022
Yes please!
bnoordhuis commentedon Feb 8, 2022
This is most likely blocked on openssl growing the necessary infrastructure.
Extensions are currently added to the handshake packet in fixed order, see tls_construct_extensions() in deps/openssl/openssl/ssl/statem/extensions.c.
A workaround is to pipe the
tls.Socketinto a duplex stream, parse the handshake packet and shuffle the extensions around. Not trivial but not impossible either.joaoscheuermann commentedon Mar 10, 2022
Do you have any exemple code on how to do it?
tniessen commentedon Mar 11, 2022
@bnoordhuis I tried implementing that based on your description in the past, but I don't see how to get past the
Finishedmessage. OpenSSL rightfully terminates the handshake withsslv3 alert bad record mac.bnoordhuis commentedon Mar 11, 2022
@tniessen The only packet that's modified is the very first one, the ClientHello, everything else is passed through verbatim. It sounds like you're also modifying subsequent packets?
tniessen commentedon Mar 11, 2022
@bnoordhuis I am only rewriting the
ClientHello, but looking at the TLS spec, theClientHellois part of theHandshake Context, which is hashed and signed for theCertificateVerifyandFinishedmessages. That seems to cause the TLS alert.joaoscheuermann commentedon Mar 17, 2022
Seems like this will not be possible with Node JS, it uses
Open SSLfor TLS.I wrapped a

Socketin aDuplex Streamto intercept and modify theClientHellorecord. At the end, im getting this error:Any Header extension that SSL doesn't requires throw this error... Im not aware of any config options that allow OpenSSL to ignore any headers/extensions that is not requested...
bnoordhuis commentedon Sep 15, 2022
I've filed feature request openssl/openssl#19220.
10 remaining items
pimterry commentedon Dec 18, 2024
Just adding an update here: there's now notable sites (like php.net) which block all requests from modern Node.js completely by TLS fingerprint. Discussion here: nodejs/help#4516.
The popularity of Node probably means there's lots of people sending bot traffic with Node, and so increasingly it's going to get flagged as a suspicious fingerprint in itself and blocked by CDNs all over the place. I haven't had much time to work on this recently, but there's some notes in my comments above if anybody has time to help work towards solutions to this.
pimterry commentedon Apr 3, 2025
@bnoordhuis You closed this back in 2022, but I'm going to reopen it if that's OK (please LMK if you disagree) because:
Current state:
secureOptions.SSL_CTX_add_custom_extsomehow, sufficiently to allow adding custom extensions to match fingerprints in full. I'm planning on working on that too later this year, when I have a proper chunk of time (but contributions from others in the meantime are very welcome), so it's ready by the time we update to OpenSSL 3.6 or later (which would be 2026 at the earliest).On the current path, OpenSSL's TLS handshake will have no non-configurable distinguishing features by version 3.6 (which is nice for reducing fingerprintability in general), and with custom extensions support in Node in theory it'll be possible to match a wide range of common TLS fingerprints, bringing JS to more-or-less parity with the Go-based approaches. Much of this is low-level advanced TLS options that most users shouldn't be using manually, but should act as a foundation for userland pure-JS solutions analogous to https://github.com/Danny-Dasilva/CycleTLS.
github-actions commentedon Oct 1, 2025
There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the never-staleIssues and PRs exempt from automated stale handling.
label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment.
For more information on how the project manages feature requests, please consult the feature request management document.
pimterry commentedon Oct 1, 2025
Marked
never-stale. See my comment above for a status update - this is going to take a really long time, because of the chain of quite complex internals that need to be updated first. Quite a few core fixes are in place, but the key step is that we need to update to OpenSSL 3.6+ to use them, which will take some time! There's other work to do here in the meantime to get everything else ready before then, but that's the current ETA and this can stay open until then.With the other work done, it may be possible to use OpenSSL 3.6 in the meantime with a custom build of Node to start testing this, but hard to say for now, YMMV.
pimterry commentedon Mar 15, 2026
I've been making very good progress on this recently: with #62217 and #62254, it will be possible to sufficiently control the fingerprint of a Node TLS connection to exactly match the JA4 fingerprints for Chrome, Firefox or curl (and basically anything else AFAIK - those are just my goto test cases).
JA3 has been obsoleted by Chrome's random extension ordering & GREASE work, but we will eventually get relatively close to that anyway - with the next OpenSSL bump (Node v27 I think) we'll get my fixes for openssl/openssl#26990 EC point formats which is the main outstanding gap there. All the changes here will generally make Node's TLS less distinctive, so even the default fingerprint will be much closer to standard practices everywhere else.
Full fingerprint control won't be available directly within Node itself (there's plenty of weirdness & footguns here, supporting this as a built-in isn't a good idea) but it'd be easy to implement from a native addon that could integrate into the normal Node TLS APIs - so you can use standard TLS/HTTPS APIs or anything that depends on them, but control the fingerprint at the same time.
Once the above PRs are merged & released, I'll publish a package using them to do this, and then finally we can close this issue (after just 5 short years...). If anybody wants to review those to help find & finish up any issues there that'd be very welcome.
addaleax commentedon Mar 20, 2026
Btw, I asked this quite some time ago, but this still seems relevant for TLS client fingerprinting: #54787
pimterry commentedon Mar 23, 2026
Good tip, thanks @addaleax! I hadn't seen that, I agree we very likely don't need to add this extension for client connections if browsers aren't doing it, it's just extra traffic and fingerprinting surface (even down to individual client fingerprinting, which is a privacy issue in itself). I'll open a PR to cover that case too later this week.
pimterry commentedon Jul 9, 2026
The final key part of this shipped in Node 26.4.0, and it's now possible to fully match most common JA4 TLS fingerprints in Node. I've published a standalone module which glues it all together for practical use: https://github.com/httptoolkit/node-tls-impersonate.
Pass a fingerprint spec there (or capture one with https://github.com/httptoolkit/read-tls-client-hello/) and it'll give you TLS options that Node's built-in APIs will accept to configure the TLS connection. Capable of matching standard Android traffic fingerprints, latest Chrome, Firefox, Curl, etc, without breaking anything or reducing the actual security of the connection.
Can't perfectly match current Safari (due to some outdated TLS options completely unsupported nowadays) but it looks very likely they'll drop those soon to become compatible as well: https://eclecticlight.co/2026/04/23/networking-changes-coming-in-macos-27/.
I'll close this. Some remaining caveats to be aware of: