LayerZeroFault
wallet security-fixes

Fix: WalletConnect wc_sessionAuthenticate CACAO Signature Bypass

VV

Written by

Fact-Checked on September 11, 2026

Verified Expert

Fix: WalletConnect wc_sessionAuthenticate CACAO Signature Bypass

With the widespread adoption of WalletConnect v2 One-Click Auth (implementing CAIP-74 and EIP-4361 Sign-In with Ethereum), dApps can combine session establishment and wallet identity verification into a single QR-code scan or signature prompt.

However, formal protocol analysis and security audits have uncovered a critical vulnerability in the handling of wc_sessionAuthenticate: when a peer wallet transmits an invalid, expired, or corrupted signature inside the CACAO (Chain-Agnostic Authenticated Content Object) payload, client SDKs frequently persist the session in storage and return an active session object. Furthermore, valid signatures are not strictly cross-referenced against the dApp’s original challenge parameters (such as domain, nonce, and aud).

If your application is also securing iframe communication against unverified cross-window messages, review our guide on WalletConnect postMessage Listener Missing event.origin Validation.


Cryptographic Mechanics: CAIP-74 and the CACAO Structure

A CACAO token encapsulates a standard EIP-4361 SIWE message into a structured format designed for multi-chain relaying:

CACAO Object Structure:
├── h: Header  -> { t: "eip4361" }
├── p: Payload -> { iss, aud, domain, nonce, iat, exp, statement, resources }
└── s: Sig     -> { t: "eip191" | "eip1271", s: "<hex_signature>" }

Placeholder: Sequence Diagram of wc_sessionAuthenticate Handshake and CACAO Verification Vulnerability

The Vulnerability Vectors

Formal verification using protocol modeling engines (such as Verifpal) revealed two fundamental implementation weaknesses:

  1. Failure to Reject Invalid Signatures: If verifySignature(cacao) throws an exception or evaluates to false, older SDK controller handlers catch the error internally, emit a warning log, yet continue executing the session persistence routine:
    // Vulnerable internal pattern in SignClient Auth Controller
    const isValid = await this.verifyCacao(authResponse.cacao).catch(() => false);
    // VULNERABILITY: If isValid is false, the session is STILL constructed!
    const session = this.createSessionFromAuth(authResponse);
    return { session, cacao: authResponse.cacao };
  2. Missing Request Parameter Binding: The wallet signs a CACAO payload generated on the wallet’s device. If the wallet tampers with the domain, swaps the nonce, or targets a different chainId, the dApp client fails to verify that response.cacao.p.nonce === request.nonce, opening doors to replay and cross-dApp impersonation attacks.

Step-by-Step Remediation Protocol

To ensure that only cryptographically authentic, untampered sessions are authorized in your production dApp or node backend, enforce the following defensive pipeline.

Placeholder: Architecture Diagram of Strict CACAO Verification Middleware and Nonce Validation

1. Enforce Strict Server-Side SIWE / CACAO Validation

Never rely exclusively on client-side SDK verification flags. Validate the CACAO envelope on your API backend before issuing JSON Web Tokens (JWT) or granting authenticated access:

// server/auth/verifyCacao.ts
import { verifySiweMessage } from 'viem/siwe';
import { formatSiweMessage } from './cacaoUtils';

interface CacaoPayload {
  h: { t: string };
  p: {
    iss: string; // "did:pkh:eip155:1:0x1234..."
    domain: string;
    aud: string;
    nonce: string;
    iat: string;
  };
  s: { t: string; s: string };
}

export async function validateCacaoSession(
  cacao: CacaoPayload, 
  expectedNonce: string, 
  expectedDomain: string
): Promise<boolean> {
  // 1. Nonce Matching Check
  if (cacao.p.nonce !== expectedNonce) {
    throw new Error('AUTH_REJECTED: CACAO nonce mismatch or expired.');
  }

  // 2. Domain Binding Check
  if (cacao.p.domain !== expectedDomain) {
    throw new Error('AUTH_REJECTED: CACAO domain does not match application origin.');
  }

  // 3. Extract Ethereum Address from DID PKH
  const parts = cacao.p.iss.split(':');
  const address = parts[4] as `0x${string}`;

  // 4. Reconstruct Canonical EIP-4361 Message String
  const reconstructedMessage = formatSiweMessage({
    domain: cacao.p.domain,
    address,
    statement: cacao.p.statement,
    uri: cacao.p.aud,
    version: '1',
    chainId: parseInt(parts[2], 10),
    nonce: cacao.p.nonce,
    issuedAt: cacao.p.iat,
  });

  // 5. Cryptographic Signature Verification (supports EIP-191 & EIP-1271 Smart Accounts)
  const isValid = await verifySiweMessage({
    message: reconstructedMessage,
    signature: cacao.s.s as `0x${string}`,
    domain: expectedDomain,
    nonce: expectedNonce,
  });

  if (!isValid) {
    throw new Error('AUTH_REJECTED: Cryptographic signature validation failed for CACAO.');
  }

  return true;
}

2. Client-Side Defensive Interceptor for signClient

In your frontend application or automated agent script, wrap signClient.authenticate() with explicit assertion gates:

// src/auth/safeAuthenticate.ts
import { SignClient } from '@walletconnect/sign-client';

export async function safeSessionAuthenticate(
  signClient: InstanceType<typeof SignClient>,
  authParams: any
) {
  const generatedNonce = authParams.nonce;
  
  // Initiate authentication handshake
  const authResponse = await signClient.authenticate(authParams);
  
  const { cacao } = authResponse;
  if (!cacao || !cacao.s || !cacao.s.s) {
    await signClient.disconnect({
      topic: authResponse.session?.topic || '',
      reason: { code: 4001, message: 'Missing CACAO signature' }
    });
    throw new Error('Authentication rejected: Wallet did not provide a signature.');
  }

  // Validate nonce binding
  if (cacao.p.nonce !== generatedNonce) {
    // Teardown the fraudulently initialized session immediately
    if (authResponse.session) {
      await signClient.disconnect({
        topic: authResponse.session.topic,
        reason: { code: 4001, message: 'Nonce mismatch' }
      });
    }
    throw new Error('CRITICAL SECURITY: Received CACAO nonce does not match generated challenge!');
  }

  return authResponse;
}

Security Verification Matrix

Vulnerability CheckAttack / Test ScenarioExpected Defensive Behavior
Invalid Signature StringWallet returns 0x0000... dummy signatureSDK immediately purges topic; promise rejects; zero session persisted in storage.
Replay Old CACAORe-submit CACAO from previous sessionNonce verification fails; server returns 401 Unauthorized.
Tampered DomainCACAO signed for evil.com instead of your dAppDomain validation check aborts handshake before session establishment.
EIP-1271 Contract WalletSmart Account (Safe, Biconomy) signs CACAOverifySiweMessage queries contract isValidSignature on-chain; validates successfully.

Frequently Asked Questions

Q: Why was this bug present in the core WalletConnect protocol?

In the initial transition from standard pairing (wc_sessionPropose) to unified authentication (wc_sessionAuthenticate), session management code was optimized for low-latency UX. The assumption was that application backends would always perform independent signature validation, but many dApps relied entirely on the SDK’s local return values.

Q: Does this vulnerability affect standard WalletConnect v2 pairings (eth_requestAccounts)?

No. Standard pairings establish an encrypted communication channel first and request signatures via independent RPC calls (personal_sign / eth_signTypedData). The vulnerability is isolated to the wc_sessionAuthenticate (One-Click Auth) method implementing CAIP-74.

Q: Can smart contract wallets (ERC-4337 / Gnosis Safe) use CACAO authentication?

Yes. CACAO supports s.t: "eip1271". The verifier must execute an on-chain isValidSignature(bytes32 hash, bytes signature) call against the account contract address rather than recovering the public key with standard ecrecover.

Partner Spotlight: Gate.io

Trade Securely on Gate.io

Don't risk your assets on centralized silos or unverified endpoints. Trade securely on Gate.io with deep liquidity and institutional-grade security protocols.

Claim $100 Sign-up Bonus

Official Partner Referral Link

Related Inquiries

What is the CACAO signature bypass in WalletConnect wc_sessionAuthenticate?

In WalletConnect's One-Click Auth implementation (CAIP-74), when a wallet returns an authentication response with an invalid or malformed signature, the client SDK accepts the response payload and persists the session without properly asserting signature validity or checking that the signed payload matches the original auth request parameters (nonce, domain, chainId).

How can malicious actors exploit this authentication flaw?

An attacker running a rogue wallet or intermediate relayer can return forged CACAO approval objects containing arbitrary Ethereum addresses. If the dApp backend relies on the client-side sessionAuthenticate response without standalone server-side SIWE signature verification, unauthorized user sessions can be created.

How do I secure my dApp against CACAO auth spoofing?

Implement strict server-side or local cryptographic verification of the CACAO signature using Viem verifySiweMessage or Ethers verifyMessage, validate that the returned CACAO nonce strictly matches your issued cryptographic challenge, and reject session initialization if verification fails.