[BUG] No TLS configuration escape hatch — Bun runtime ignores NODE_OPTIONS, corporate firewalls block connections

Resolved 💬 3 comments Opened Mar 21, 2026 by WYSIATI Closed Mar 21, 2026

Summary

Claude Code's embedded Bun runtime ignores \NODE_OPTIONS\ (e.g., \--tls-max-v1.2\), leaving users with no way to configure TLS behavior. This makes Claude Code unusable on:

  • Corporate networks with JA3/JA4 TLS fingerprint-based firewalls (#21652)
  • WSL2 environments with TLS 1.3 decode errors (#34686)

Curl, Node.js, and browsers work fine on these networks — only Bun is blocked.

Root Cause

  1. Corporate TLS fingerprinting: Palo Alto, Fortinet, Zscaler firewalls identify connections by TLS Client Hello fingerprint (JA3/JA4). Bun's TLS fingerprint differs from standard browsers/Node.js, triggering RST
  2. WSL2 TLS 1.3 bug: WSL2's network stack has a known issue with TLS 1.3 decode errors (\alert 50\). \NODE_OPTIONS="--tls-max-v1.2"\ fixes Node.js apps but is ignored by Bun
  3. No escape hatch: No Claude Code-specific env var or CLI flag to control TLS version or cipher suites

Evidence from #21652:
| Test | Result |
|------|--------|
| curl to api.anthropic.com | HTTP 404 (works) |
| Node.js v18/v20 fetch | HTTP 404 (works) |
| Claude Code (Bun) | ECONNRESET (blocked) |
| Mobile tethering | Works |
| VPN | Works |

Proposed Fix

Add Claude Code-specific TLS configuration:

import tls from 'tls';

function configureTLS() {
  const maxVersion = process.env.CLAUDE_CODE_TLS_MAX_VERSION;
  if (maxVersion && ['TLSv1.2', 'TLSv1.3'].includes(maxVersion)) {
    tls.DEFAULT_MAX_VERSION = maxVersion;
    debug(\`TLS max version: \${maxVersion}\`);
  }

  const ciphers = process.env.CLAUDE_CODE_TLS_CIPHERS;
  if (ciphers) {
    tls.DEFAULT_CIPHERS = ciphers;
    debug('TLS ciphers overridden');
  }

  const minVersion = process.env.CLAUDE_CODE_TLS_MIN_VERSION;
  if (minVersion && ['TLSv1.2', 'TLSv1.3'].includes(minVersion)) {
    tls.DEFAULT_MIN_VERSION = minVersion;
  }
}

// CLI flag for convenience:
// claude --tls-compat
// Equivalent to CLAUDE_CODE_TLS_MAX_VERSION=TLSv1.2

// Diagnostics in --verbose:
function logTLSDiagnostics() {
  debug(\`TLS: max=\${tls.DEFAULT_MAX_VERSION} min=\${tls.DEFAULT_MIN_VERSION}\`);
  debug(\`TLS: ciphers=\${tls.DEFAULT_CIPHERS ? 'custom' : 'default'}\`);
  debug(\`TLS: CA=\${process.env.NODE_EXTRA_CA_CERTS || 'system'}\`);
  debug(\`Runtime: \${typeof Bun !== 'undefined' ? 'Bun' : 'Node.js'}\`);
}

Environment Variables

| Variable | Values | Default | Purpose |
|----------|--------|---------|---------|
| \CLAUDE_CODE_TLS_MAX_VERSION\ | \TLSv1.2\, \TLSv1.3\ | \TLSv1.3\ | Cap TLS version |
| \CLAUDE_CODE_TLS_MIN_VERSION\ | \TLSv1.2\, \TLSv1.3\ | \TLSv1.2\ | Floor TLS version |
| \CLAUDE_CODE_TLS_CIPHERS\ | OpenSSL cipher string | system default | Override cipher suites |

CLI Flag

\claude --tls-compat\ — shortcut for \CLAUDE_CODE_TLS_MAX_VERSION=TLSv1.2\. Most users won't know which ciphers to set; this provides a one-step fix.

Testing Plan

Unit Tests

| Test | Input | Expected |
|------|-------|----------|
| Sets max version | \CLAUDE_CODE_TLS_MAX_VERSION=TLSv1.2\ | \tls.DEFAULT_MAX_VERSION\ = 'TLSv1.2' |
| Sets ciphers | \CLAUDE_CODE_TLS_CIPHERS=...\ | Custom ciphers applied |
| No-op without env vars | No vars set | Defaults unchanged |
| Invalid version rejected | \=SSLv3\ | Warning logged, default kept |
| \--tls-compat\ parsed | CLI flag | TLS 1.2 max set |
| Diagnostics output | \logTLSDiagnostics()\ | Shows version, ciphers, CA, runtime |

Integration Tests

| Test | Setup | Expected |
|------|-------|----------|
| API call with TLS 1.2 | Set max=TLSv1.2 | Connects with TLS 1.2 |
| API call with default | No vars | Connects with TLS 1.3 |
| Config applies to all connections | Set vars | API + OTEL + MCP all use configured TLS |
| Flag + env var precedence | Both set | CLI flag wins |

E2E Tests

| Test | Steps | Expected |
|------|-------|----------|
| WSL2 workaround | WSL2 + \CLAUDE_CODE_TLS_MAX_VERSION=TLSv1.2\ | Connects successfully |
| Corporate firewall | Network with JA3 blocking + \--tls-compat\ | Connects |
| No regression | Default config, normal usage | Works as before |

Related Issues

  • #21652 — ECONNRESET on corporate network (TLS fingerprinting)
  • #34686 — ECONNRESET on WSL2 (TLS 1.3 decode error)
  • #4297 — API Error (Connection error.) on Windows

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗