CVE-2026-96770 Overview
CVE-2026-96770 is a certificate validation flaw in Temporal's s2s-proxy, affecting all published versions through 0.2.2. In versions 0.1.16 through 0.2.2, TLS server listeners configured with skipCAVerification set to false use Go's RequireAnyClientCert mode. This mode confirms the client possesses the certificate's private key but does not verify the certificate against the configured Certificate Authority (CA). An attacker can present a self-signed certificate and key to establish TLS and yamux connections. The attacker can then invoke any Remote Procedure Call (RPC) permitted by the proxy configuration and Temporal credentials, without holding any trusted certificate or valid Temporal credential.
Critical Impact
Unauthenticated network attackers can bypass mutual TLS enforcement in s2s-proxy and invoke Temporal RPCs using self-signed certificates.
Affected Products
- Temporal s2s-proxy versions 0.1.16 through 0.2.2
- Deployments relying on RequireClientAuth for mutual TLS enforcement
- Temporal cross-cluster replication and codec server topologies using s2s-proxy
Discovery Timeline
- 2026-09-23 - CVE-2026-96770 published to the National Vulnerability Database (NVD)
- 2026-09-23 - Last updated in NVD database
Technical Details for CVE-2026-96770
Vulnerability Analysis
The defect is an Improper Following of a Certificate's Chain of Trust weakness [CWE-296] in the TLS server configuration logic of s2s-proxy. When skipCAVerification is false, the server sets tls.Config.ClientAuth to tls.RequireAnyClientCert. Go's standard library treats this mode as a proof-of-possession check only. The server confirms that the peer holds the private key matching the presented certificate but never validates issuer, signature chain, or the configured ClientCAs pool.
Once the TLS handshake completes, the proxy multiplexes traffic over yamux and accepts inbound RPCs. Any RPC scoped by the proxy's routing configuration and available Temporal credentials becomes reachable to the attacker. Because no trusted client certificate or Temporal token is required, the vulnerability collapses the intended zero-trust boundary between Temporal clusters.
Root Cause
The root cause is the use of tls.RequireAnyClientCert instead of tls.RequireAndVerifyClientCert in encryption/tls.go. The ClientCAs pool was populated correctly, but the enforcement mode never consulted it during the handshake.
Attack Vector
The attack requires only network reachability to an exposed s2s-proxy TLS listener. An attacker generates a self-signed keypair, initiates a TLS handshake, upgrades to yamux, and issues configured RPC calls. No authentication material trusted by the deployment is needed.
// Patch: encryption/tls.go - Verify client certificate against configured CA (#296)
tlsConfig = auth.NewEmptyTLSConfig()
if !serverConfig.SkipCAVerification {
- tlsConfig.ClientAuth = tls.RequireAnyClientCert
+ tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs, err = fetchCACert(serverConfig.RemoteCAPath)
if err != nil {
return nil, fmt.Errorf("failed to read CACert from %s: %w", serverConfig.RemoteCAPath, err)
Source: GitHub Commit f1dc6b9
Detection Methods for CVE-2026-96770
Indicators of Compromise
- Successful TLS handshakes on s2s-proxy listeners where the client certificate issuer does not match any configured CA in RemoteCAPath or ClientCAPath.
- yamux session establishment followed by RPC invocations from source IP addresses outside the expected cluster peer list.
- Unexpected Temporal namespace or workflow RPC activity attributed to the s2s-proxy identity.
Detection Strategies
- Enable verbose TLS logging on s2s-proxy and correlate presented client certificate subjects and issuers against the deployment's trusted CA inventory.
- Monitor Temporal audit logs for RPC calls originating through s2s-proxy that do not correspond to authorized peer clusters.
- Inventory running s2s-proxy binaries and confirm versions are outside the vulnerable 0.1.16 through 0.2.2 range.
Monitoring Recommendations
- Alert on new or unexpected peer IP addresses establishing yamux sessions with s2s-proxy listeners.
- Track certificate fingerprints observed at the listener and flag any self-signed or non-CA-issued certificates.
- Capture network flow metadata for the s2s-proxy port and baseline expected replication traffic volume and endpoints.
How to Mitigate CVE-2026-96770
Immediate Actions Required
- Upgrade s2s-proxy to a version that includes the fix from pull request #296, which enforces tls.RequireAndVerifyClientCert.
- Restrict network exposure of s2s-proxy listeners to known peer cluster IP ranges using firewalls or security groups.
- Rotate any Temporal credentials and mTLS certificates that were reachable through vulnerable proxy deployments.
Patch Information
The fix is delivered in two upstream commits. Commit d100c22 corrects TLS configuration translation so that SkipCAVerification is derived from RequireClientAuth. Commit f1dc6b9 changes the server's ClientAuth mode from tls.RequireAnyClientCert to tls.RequireAndVerifyClientCert, ensuring the certificate chain is validated against ClientCAs.
// Patch: config/config.go - Fix TLS handshake for mux-internal proxy server (#182)
func FromServerTLSConfig(cfg ServerTLSConfig) encryption.TLSConfig {
return encryption.TLSConfig{
- CertificatePath: cfg.CertificatePath,
- KeyPath: cfg.KeyPath,
- RemoteCAPath: cfg.ClientCAPath,
- VerifyCA: cfg.RequireClientAuth,
+ CertificatePath: cfg.CertificatePath,
+ KeyPath: cfg.KeyPath,
+ RemoteCAPath: cfg.ClientCAPath,
+ SkipCAVerification: !cfg.RequireClientAuth,
}
}
Source: GitHub Commit d100c22
Workarounds
- Terminate s2s-proxy TLS behind an external mTLS-enforcing reverse proxy or service mesh sidecar that validates client certificates against the deployment CA.
- Isolate s2s-proxy listeners on private networks reachable only by authorized Temporal peer clusters until the patch is applied.
- Reduce the RPC surface exposed through s2s-proxy configuration to the minimum set required for replication or codec operations.
# Verify installed s2s-proxy version and upgrade to a patched release
s2s-proxy --version
go install github.com/temporalio/s2s-proxy@latest
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.
