← Back to Blog

Smart Contract Wallet Security: Gnosis Safe Vulnerabilities, Module Exploits, and Signature Risks

2026-04-18 gnosis safe multisig smart wallet module guard signature solidity security

Gnosis Safe is the most widely deployed smart contract wallet in production. At the time of writing, it secures tens of billions of dollars across thousands of protocol treasuries, DAO multisigs, and team wallets. Its architecture—modular, extensible, and upgradeable—is precisely what makes it attractive. It is also precisely what makes it dangerous when misconfigured.

A raw EOA multisig offers one attack surface: private key compromise. Safe introduces an entirely different threat model. The wallet itself is a smart contract, and every extension point—modules, guards, fallback handlers, delegate calls—is an additional surface that can be weaponized. Security auditors who treat Safe as equivalent to a hardware wallet are missing the picture. The security guarantee lives not just in the threshold of signatures, but in the integrity of every component bolted onto the Safe.

This post walks through six concrete vulnerability classes that affect Gnosis Safe deployments, with code context, attack mechanics, and hardening patterns for each. These are not theoretical: each pattern maps to real incidents or disclosed vulnerabilities.


1. Malicious Module Installation

Safe modules are contracts granted the right to execute transactions on behalf of the Safe—without requiring the normal owner signature threshold. Once a module is enabled, it can call execTransactionFromModule freely.

// GnosisSafe.sol (simplified)
function execTransactionFromModule(
    address to,
    uint256 value,
    bytes calldata data,
    Enum.Operation operation
) public returns (bool success) {
    require(modules[msg.sender] != address(0), "GS104");
    // No signature check. No guard call (see vulnerability 3).
    success = execute(to, value, data, operation, gasleft());
}

The attack vector is social engineering at the governance layer. An attacker presents a module as a "recovery helper" or "automation contract" through a governance proposal, Discord announcement, or phishing site that spoofs a known protocol's UI. Owners sign the enableModule transaction believing they are approving something benign. Once installed, the module drains the Safe in a follow-up transaction—no further signatures required.

This exact pattern played out in protocol treasury attacks where a single malicious module was disguised as a yield-routing contract. By the time owners noticed anomalous outflows, the funds were bridged.

Hardening patterns:

Implement a timelock on module installation. Any enableModule call should require a mandatory waiting period (48–72 hours minimum for high-value Safes) before the module becomes active. During that window, owners who did not vote for the proposal can organize a veto.

// TimelockModule.sol pattern
mapping(address => uint256) public pendingModules;
uint256 public constant TIMELOCK = 48 hours;

function proposeModule(address module) external onlyOwner {
    pendingModules[module] = block.timestamp + TIMELOCK;
}

function activateModule(address module) external {
    require(block.timestamp >= pendingModules[module], "Timelock active");
    safe.enableModule(module);
}

Maintain an off-chain (and ideally on-chain) registry of all active modules. Run periodic audits: enumerate getModulesPaginated and verify each module address against a known-good list. Any unknown address is an immediate incident.


2. Signature Replay on Chain Forks

Safe encodes chainId in its transaction hash via EIP-712, which is the correct defense against naive cross-chain replay. The hash includes the chain ID, the Safe address, the nonce, and the calldata.

bytes32 safeTxHash = keccak256(
    abi.encodePacked(
        bytes1(0x19),
        bytes1(0x01),
        domainSeparator(), // includes chainId
        keccak256(abi.encode(
            SAFE_TX_TYPEHASH,
            to, value, keccak256(data),
            operation, safeTxGas, baseGas,
            gasPrice, gasToken, refundReceiver,
            _nonce
        ))
    )
);

The residual risk appears during hard forks that copy state. When Ethereum Classic split from Ethereum, both chains shared identical state—including Safe deployments at the same addresses with the same nonces and the same owner keys. A transaction signed for nonce N on chain A could be replayed on chain B if the nonce had not advanced on B, because the chain ID in the domain separator reflected the pre-fork chain ID.

The same risk applies to testnets that are periodically reset, cross-chain deployments where the same deployer key and CREATE2 salt produce an identical Safe address on multiple chains, and chains that fork from a mainnet state snapshot.

Hardening patterns:

After any fork event, immediately submit a no-op transaction on the minority chain to advance the nonce before any attacker can replay a stale signature. Ensure every signing operation confirms the chainId in the EIP-712 domain separator matches the expected network. Upgrade to the latest Safe version—Safe 1.3.0+ enforces the chain ID correctly and includes it in the domain separator by default.

For deployments on multiple chains, treat each chain's Safe as an entirely separate security domain. Never reuse signatures across chains, even if the addresses match.


3. Guard Bypass via Module

Safe supports transaction guards—contracts that implement IGuard and are called before and after every owner-signed transaction. Guards can enforce spending limits, block certain function selectors, restrict recipient addresses, or implement circuit breakers.

interface IGuard {
    function checkTransaction(
        address to, uint256 value, bytes memory data,
        Enum.Operation operation, uint256 safeTxGas,
        uint256 baseGas, uint256 gasPrice, address gasToken,
        address payable refundReceiver, bytes memory signatures,
        address msgSender
    ) external;

    function checkAfterExecution(bytes32 txHash, bool success) external;
}

The critical flaw is that execTransactionFromModule does not call the guard. This is documented behavior, but its security implications are systematically underestimated. Any protocol that installs a guard to enforce invariants—spending caps, approved recipient lists, paused states—while also permitting any module, has a bypassed guard.

// execTransaction calls guard:
if (guard != address(0)) {
    Guard(guard).checkTransaction(...);
}

// execTransactionFromModule does NOT:
function execTransactionFromModule(...) public returns (bool) {
    require(modules[msg.sender] != address(0), "GS104");
    // guard is never called here
    success = execute(to, value, data, operation, gasleft());
}

An attacker who controls or compromises any enabled module can route around the guard entirely, regardless of how carefully the guard logic is written.

Hardening patterns:

Audit the interaction between all installed modules and the guard's intended invariants. If the guard enforces a spending limit, the module must implement that same limit internally—it cannot rely on the guard to enforce it. Where possible, encode the guard's invariants directly in the module. Treat any module as implicitly owning the same trust level as the guard bypass itself.

If the guard's enforcement is mandatory for protocol correctness, disable all modules or restrict modules to only calling pre-approved functions that the guard would permit anyway.


4. Fallback Handler Exploitation

Safe routes calls with unknown function selectors to its fallback handler. This mechanism exists to support ERC-1155, EIP-1271 signature validation, and other standards that the core Safe contract does not natively implement.

// GnosisSafe fallback
fallback() external {
    bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT;
    assembly {
        let handler := sload(slot)
        if iszero(handler) { return(0, 0) }
        calldatacopy(0, 0, calldatasize())
        // delegate to handler
        let success := call(gas(), handler, 0, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        if iszero(success) { revert(0, returndatasize()) }
        return(0, returndatasize())
    }
}

A malicious or vulnerable fallback handler can intercept any call to an unknown selector on the Safe and execute arbitrary logic. If the handler is set to a contract the attacker controls, they can make the Safe appear to support any interface, forge isValidSignature responses for EIP-1271 checks, or perform state mutations on behalf of the Safe.

In practice, a vulnerable fallback handler was at the root of several EIP-1271 signature forgery incidents, where an attacker set a custom handler that returned 0x1626ba7e (the EIP-1271 magic value) for any input, allowing unauthorized approvals in protocols that relied on on-chain signature validation.

Hardening patterns:

If the Safe does not require a fallback handler, explicitly set it to address(0). Only install handlers from the official Safe contract repository after reviewing their source. Never install a fallback handler that was provided by a third party without an independent audit. When upgrading Safe versions, verify the fallback handler address is preserved or intentionally updated.


5. Social Engineering via Transaction Simulation Discrepancy

This vulnerability is not a code bug—it is a human-in-the-loop attack that exploits the gap between what owners see and what they sign.

The attack workflow: an attacker constructs a transaction where the calldata encodes a complex function call—perhaps a multicall or an interaction with a proxy. They generate a Tenderly simulation that shows the transaction behaving benignly: token balances unchanged, no unexpected transfers. The simulation may be genuine for an early block but will differ at execution time due to state changes the attacker controls, or the calldata may call a different function than the one shown in the simulation UI.

Owners in a DAO multisig receive a Tenderly link in a Notion page or Discord. They review the simulation, see no red flags, and sign. The actual transaction drains the treasury.

The mechanism is possible because Tenderly simulations run against a specific block state, and because Safe's UI often shows a human-readable summary derived from ABI decoding—but the ABI used for decoding may not match the actual contract being called if the attacker uses a proxy, a selector collision, or a forged ABI hint.

// Selector collision example:
// transfer(address,uint256) and gimmeMoney(bytes32,uint256)
// both produce selector 0xa9059cbb if the attacker engineers the collision
bytes4 selector = bytes4(keccak256("gimmeMoney(bytes32,uint256)"));
// == bytes4(keccak256("transfer(address,uint256)")) in a crafted contract

Hardening patterns:

Always decode calldata independently of the simulation tool. Use cast --calldata-decode or a standalone decoder before signing. Hardware wallet signers should confirm the function selector and first parameter on the device screen. Establish a policy: no transaction is approved based solely on a simulation link. At least one signer must independently decode the calldata and confirm the target address on-chain using a block explorer.

For high-value Safes, use Safe's built-in transaction builder and verify decoded parameters match the stated intent before any owner signs.


6. 1-of-N Emergency Path Compromises the Multisig Guarantee

The most common governance mistake in Safe deployments: a 3-of-5 or 4-of-7 Safe for normal operations, with a documented "emergency" path that allows a single owner or a separate admin key to execute without the normal threshold.

This pattern appears as a dedicated module with a 1-of-N threshold, an owner with special delegatecall access, or a separate Safe where one owner holds all keys for "incident response." The intent is legitimate: teams need to respond quickly to exploits. The implementation undermines the entire security model.

// Anti-pattern: emergency module with no threshold
contract EmergencyModule {
    address public emergencyAdmin;
    GnosisSafe public safe;

    function emergencyWithdraw(address token, address to, uint256 amount)
        external
    {
        require(msg.sender == emergencyAdmin, "Not admin");
        // One key bypasses the 3/5 threshold entirely
        bytes memory data = abi.encodeWithSignature(
            "transfer(address,uint256)", to, amount
        );
        safe.execTransactionFromModule(token, 0, data, Enum.Operation.Call);
    }
}

If the emergency admin key is compromised—through phishing, malware, or physical access—the attacker has unconditional access to all Safe funds. The 3-of-5 threshold that cost so much coordination to enforce is now worthless.

Hardening patterns:

Emergency paths must require the same threshold as normal operations, or higher. If speed is the concern, implement a timelocked cancel mechanism instead: normal operations require 3-of-5 with a 24-hour delay, and any 2-of-5 can cancel a pending transaction during that window. This preserves rapid response without reducing the threshold.

If a lower-threshold emergency path is absolutely required by protocol design, time-lock it with a mandatory delay (minimum 1 hour) so legitimate owners can cancel before the emergency action executes.


What ContractScan Detects

ContractScan analyzes Safe deployments and associated module contracts to flag these vulnerability classes automatically.

Vulnerability Detection Method Severity
Malicious Module Installation Identifies enableModule calls without timelock guards; flags modules lacking source verification Critical
Signature Replay on Chain Forks Checks Safe version and domain separator encoding; flags pre-1.3.0 deployments High
Guard Bypass via Module Detects mismatches between installed guards and active modules; flags modules that bypass guard invariants High
Fallback Handler Exploitation Audits fallback handler address against known-safe registry; flags unverified or zero-audit handlers High
Simulation Discrepancy Attack Flags selector collisions and proxy patterns that could produce misleading simulations Medium
1-of-N Emergency Path Detects modules or owners with sub-threshold execution rights; flags missing timelocks on emergency paths Critical

Scan your Safe deployment and all associated modules at contractscan.io before the next governance vote.


Important Notes

This post is for informational and educational purposes only. It does not constitute financial, legal, or investment advice. The security analysis provided is based on available data and automated tools, which may not capture all potential vulnerabilities. Always conduct a professional audit before deploying smart contracts.

Scan your contract for this vulnerability
Free QuickScan — Unlimited quick scans. No signup required.. No signup required.
Scan a Contract →