Advanced

TLS Configuration

Rezo exposes TLS configuration through three request options:

OptionTypeWhat it does
rejectUnauthorizedbooleanWhen false, skip server-certificate validation. Default true.
secureContextSecureContext (from node:tls)A pre-built SecureContext to use instead of the default.
useSecureContextbooleanForce the adapter to use the provided secureContext. Default true when secureContext is set.

Everything else — custom CAs, client certificates for mTLS, TLS protocol version pinning, cipher selection, certificate pinning — is built on top of those three by composing a Node.js SecureContext with tls.createSecureContext().

This is intentional: a SecureContext is the single primitive Node uses to describe a TLS configuration, so handing one to Rezo gives you the full surface area of Node’s TLS implementation without a parallel option set.

Skipping Certificate Validation

For development against self-signed certificates:

const { data } = await rezo.get('https://localhost:8443/api', {
  rejectUnauthorized: false
});

Warning: Never set rejectUnauthorized: false in production. It disables all chain-of-trust validation, allowing a man-in-the-middle to present any certificate.

Custom CA Certificates

Use Node’s tls.createSecureContext() to register a private CA, then pass the result as secureContext:

import { createSecureContext } from 'node:tls';
import { readFileSync } from 'node:fs';
import { Rezo } from 'rezo';

const secureContext = createSecureContext({
  ca: readFileSync('/path/to/ca.pem')
});

const internal = rezo.create({
  baseURL: 'https://internal.company.com',
  secureContext
});

const { data } = await internal.get('/api/health');

Multiple CAs are supported by passing an array:

const secureContext = createSecureContext({
  ca: [
    readFileSync('/path/to/ca1.pem'),
    readFileSync('/path/to/ca2.pem')
  ]
});

Mutual TLS (Client Certificates)

For services that require the client to present a certificate, build a SecureContext with cert, key, and (optionally) ca:

import { createSecureContext } from 'node:tls';
import { readFileSync } from 'node:fs';

const mtls = createSecureContext({
  ca:   readFileSync('/secrets/ca-bundle.pem'),
  cert: readFileSync('/secrets/client.crt'),
  key:  readFileSync('/secrets/client.key')
});

const client = rezo.create({
  baseURL: 'https://secure.internal.company.com',
  secureContext: mtls
});

await client.get('/api/sensitive-data');

PKCS#12 Bundles

tls.createSecureContext() accepts pfx + passphrase for .p12 / .pfx bundles:

const secureContext = createSecureContext({
  pfx: readFileSync('/secrets/client.p12'),
  passphrase: 'bundle-password'
});

await rezo.get('https://secure.example.com/data', { secureContext });

TLS Version Constraints

Pin minimum and maximum TLS versions through createSecureContext:

const secureContext = createSecureContext({
  minVersion: 'TLSv1.2',  // reject TLS 1.0 / 1.1
  maxVersion: 'TLSv1.3'
});

const client = rezo.create({ secureContext });

Valid values are the strings Node accepts: 'TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'. For most applications, minVersion: 'TLSv1.2' is the safe baseline.

Cipher Suites

const secureContext = createSecureContext({
  ciphers: 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256',
  minVersion: 'TLSv1.3'
});

Certificate Pinning

Rezo does not ship a pinning option directly, but you can pin from the onTls hook by inspecting the certificate fingerprint and aborting if it doesn’t match:

import { Rezo, RezoError } from 'rezo';

const EXPECTED_FINGERPRINT = 'AB:CD:EF:01:23:...';

const client = rezo.create({
  hooks: {
    onTls: [
      (event, socket) => {
        if (event.certificate?.fingerprint !== EXPECTED_FINGERPRINT) {
          // Tear down the socket — the in-flight request will reject
          // with a network error.
          socket.destroy(new Error('TLS pinning failure'));
        }
      }
    ]
  }
});

Combine with rejectUnauthorized: true (the default) so the chain validation still has to pass before pinning kicks in.

Per-Request Override

Instance-level TLS settings are inherited by every request, but any per-request option wins:

const corp = rezo.create({ secureContext: corporateSecureContext });

// Use a different CA + client cert for this one call
await corp.get('https://partner-api.com/data', {
  secureContext: createSecureContext({
    ca: partnerCA, cert: clientCert, key: clientKey
  })
});

Stealth Profiles and TLS

When you attach a RezoStealth instance, the adapter builds a SecureContext derived from the active browser profile (cipher order, signature algorithms, ECDH curves, ALPN list, session timeout) and merges it with whatever you pass in. You generally don’t need to touch secureContext yourself when stealth is active.

import rezo, { RezoStealth } from 'rezo';

const client = rezo.create({
  stealth: RezoStealth.chrome()
});

await client.get('https://example.com');
// TLS handshake matches Chrome's exactly (subject to the runtime's
// capabilities — see Stealth → TLS Fingerprinting for caveats on
// Node's OpenSSL vs. Bun's BoringSSL).

See Stealth → TLS Fingerprinting for the full picture, including which JA3/JA4 traits actually reach the wire on Node vs. Bun.

Debug Logging

To see the negotiated TLS for a single request:

const response = await rezo.get('https://example.com', { debug: true });
// [Rezo] TLS TLSv1.3 / TLS_AES_128_GCM_SHA256 / authorized=true
// [Rezo] cert subject: CN=example.com, issuer: Let's Encrypt

For programmatic access, use the onTls hook — it carries the same data as a structured event.