Skip to content
Last updated: February 06, 2026

SCWE-105: Permit Signature Replay via Missing Domain Separator or Nonce

Stable Version v1.0

This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Relationships

Description

Improper EIP-2612 permit implementations that omit chain-specific domain separators, do not increment nonces, or ignore expiration allow signatures to be replayed across chains or multiple times. Attackers can repeatedly approve spending or re-use permits on forked networks.

Remediation

  • Build EIP-712 domain separators including name, version, chainId, and verifyingContract.
  • Maintain per-owner nonces and increment on every successful permit.
  • Enforce deadlines and reject expired signatures.

Examples

Vulnerable

pragma solidity ^0.8.0;
contract Token {
    mapping(address => uint256) public nonces;

    function permit(address owner, address spender, uint256 value, bytes calldata sig) external {
        // missing chainId/domain checks, nonce not incremented
        address signer = ecrecover(/* simplified */);
        require(signer == owner, "bad sig");
        // allowance set here...
    }
}

Fixed

pragma solidity ^0.8.0;
contract Token {
    bytes32 public DOMAIN_SEPARATOR;
    mapping(address => uint256) public nonces;

    constructor() {
        DOMAIN_SEPARATOR = keccak256(abi.encode(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256(bytes("Token")),
            keccak256(bytes("1")),
            block.chainid,
            address(this)
        ));
    }

    function permit(/* params */) external {
        // verify typed data with nonce and deadline, then increment nonce
        nonces[owner] += 1;
    }
}