Skip to content

🐛 Bug Report — Runtime APIs: node:tls key/cert are documented but ignored #7201

Description

@CssEoCAO

Summary

The Cloudflare Workers documentation for node:tls shows connect(url, { key: env.KEY, cert: env.CERT }) as a way to create a TLS connection with client key material:

https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/

However, with workerd 1.20260815.1 and on the production edge, both key / cert passed directly to tls.connect() and through createSecureContext({ key, cert }) are ignored. Even deliberately malformed PEM is accepted by the Workers runtime instead of failing during context creation.

This prevents a multi-tenant Worker from loading a tenant-specific client certificate from R2 or another secret store and presenting it for one outbound mTLS request.

Documentation inconsistency

The node:tls page documents key, cert, and createSecureContext():

https://developers.cloudflare.com/workers/runtime-apis/nodejs/tls/

But the node:https page says that key, cert, and pfx are unsupported and recommends a static mTLS binding:

https://developers.cloudflare.com/workers/runtime-apis/nodejs/https/

Minimal behavioral check

In native Node.js, this fails immediately as expected:

import { createSecureContext } from "node:tls";

createSecureContext({
  key: "-----BEGIN PRIVATE KEY-----\nINVALID\n-----END PRIVATE KEY-----",
  cert: "-----BEGIN CERTIFICATE-----\nINVALID\n-----END CERTIFICATE-----",
});

In the Workers runtime, the same invalid material does not fail and produces an empty SecureContext.

I also ran a sequential A/B test against a public mTLS endpoint using the same raw HTTP/1.1 transport and parser.

Native Node.js:

  • valid client certificate passed to connect(): HTTP 404 with the expected application error;
  • the same certificate through createSecureContext(): same HTTP 404;
  • no client certificate: HTTP 403;
  • malformed PEM: ASN.1 error before the request was written.

Remote Workers edge:

  • valid key / cert;
  • valid material through createSecureContext();
  • no client certificate;
  • malformed PEM;

All four Workers cases were indistinguishable: the request was written, the peer closed the connection, and zero response bytes were received. A public non-mTLS control using the same node:tls transport and HTTP parser returned HTTP 301, so the generic socket/parser path was working.

No private key, certificate, tenant identifier, or production payload is included in this report.

Current implementation

The source for the installed workerd version appears to confirm the behavior:

Use case

We operate a multi-tenant invoicing SaaS. Each tenant uploads its own PKCS#12/PFX A1 client certificate, stored encrypted in R2. The application can load and convert the selected PFX to PEM in memory, but it cannot present that PEM in the outbound mTLS handshake.

A static mTLS binding per tenant would require control-plane provisioning and Worker reconfiguration for every onboarding, renewal, and revocation. We need either:

  1. the documented tls.connect({ key, cert }) behavior to be implemented; or
  2. another supported per-request API that accepts in-memory client certificate material.

If dynamic client certificates are intentionally unsupported, the node:tls documentation should state this explicitly and avoid the current key / cert example.

Environment

  • Wrangler: 4.124.0
  • Bundled workerd: 1.20260815.1
  • Compatibility date: 2026-08-22
  • Compatibility flag: nodejs_compat
  • Native control: Node.js v24.13.0
  • Reproduced with wrangler dev --remote on the Cloudflare edge

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions