KAI Finds Signature Bypass in Coinbase's x402 Protocol
KAI's autonomous agent discovered a critical signature verification bypass in x402, Coinbase's open protocol for HTTP-native payments using digital currencies. The vulnerability allows an attacker to forge payment authorizations for any undeployed smart wallet without possessing the corresponding private key.
The finding was submitted through Coinbase's bug bounty program on HackerOne, where it was confirmed as a valid vulnerability. The report was closed as a duplicate of an earlier submission, meaning the same real-world bug was independently discovered by KAI's automated analysis.
What KAI Found
The verify_universal_signature function in both the Python and Go implementations of the x402 facilitator accepts ERC-6492 wrapped signatures from undeployed smart wallets without verifying the inner cryptographic signature.
When a signature is presented for an address with no deployed contract code, the function:
- Parses the ERC-6492 envelope and extracts
factory,factoryCalldata, andinnerSignature - Calls
get_code(), which returns empty because the wallet has no deployed contract - Checks
has_deployment_info(), which returnsTruebecause the factory is non-zero and calldata is non-empty - Returns
Truewithout ever validating the inner signature bytes against the message hash and signer address
This means an attacker can wrap any arbitrary 65 bytes (including all zeros) in a valid ERC-6492 envelope structure and pass signature verification for any undeployed wallet address.
Vulnerable Code
The Python facilitator in x402/mechanisms/evm/verify.py hits this path:
# verify.py - verify_universal_signature()
sig_data = parse_erc6492_signature(signature)
code = get_code(provider, signer_address)
if len(code) == 0:
# wallet is not deployed
if has_deployment_info(sig_data):
if not allow_undeployed:
raise ValueError("Undeployed smart wallet not allowed")
return (True, sig_data) # BUG: inner signature never checked
The same logic exists in the Go facilitator at mechanisms/evm/verify_universal.go:
// verify_universal.go - VerifyUniversalSignature()
if !allowUndeployed {
return false, nil, errors.New(ErrUndeployedSmartWallet + ": undeployed not allowed")
}
return true, sigData, nil // BUG: inner signature never checked
Recommended Fix
For the Python facilitator, the inner signature should be verified against the signer before returning:
--- x402/mechanisms/evm/verify.py
+++ x402/mechanisms/evm/verify.py
@@ verify_universal_signature()
if has_deployment_info(sig_data):
if not allow_undeployed:
raise ValueError("Undeployed smart wallet not allowed")
- return (True, sig_data)
+ if len(sig_data.inner_signature) == 65:
+ valid = verify_eoa_signature(hash, sig_data.inner_signature, signer_address)
+ return (valid, sig_data)
+ return (False, sig_data)
For the Go facilitator:
--- mechanisms/evm/verify_universal.go
+++ mechanisms/evm/verify_universal.go
@@ VerifyUniversalSignature()
if !allowUndeployed {
return false, nil, errors.New(ErrUndeployedSmartWallet + ": undeployed not allowed")
}
- return true, sigData, nil
+ return false, sigData, nil
The Impact
The vulnerability directly impacts digital currency payments (USDC and other EIP-3009 tokens) processed through x402:
- The facilitator's
verify()endpoint accepts the forged authorization as valid, causing the resource server to serve paid content before settlement - Settlement subsequently fails on-chain because the signature is cryptographically invalid
- The attacker receives the resource without payment
- Both the Python and Go facilitator implementations are affected
This is particularly relevant because Coinbase Smart Wallet uses counterfactual deployment, where wallets exist at a deterministic address before any contract is deployed on-chain. A growing number of x402 payers will have undeployed wallet addresses, making them valid targets for this bypass.
Bug Bounty Confirmation
The finding was submitted through Coinbase's HackerOne program. Triage confirmed it as a valid vulnerability affecting both the Python and Go codepaths, with the same root cause and impact as a previously reported issue. Multiple independent researchers had filed reports for the same bug, and KAI's was closed as a duplicate of the original submission.