Skip to main content
Sigvex

Governance Attacks

Detects vulnerabilities in DAO governance mechanisms including flash loan-based voting, timelock bypass, quorum manipulation, delegation exploits, treasury drain, and emergency action abuse.

Governance Attacks

Overview

Remediation Guide: How to Fix Governance Attacks

The governance attack detector identifies vulnerabilities in DAO governance mechanisms. On-chain governance systems — where proposals are created, voted on, and executed via smart contract — are high-value targets because a successful governance attack grants the attacker control over protocol parameters, treasury funds, and upgrade authority. Sigvex identifies six distinct vulnerability classes in governance contracts:

  1. Flash loan voting: Voting weight computed from token balance at the moment of the vote call rather than at proposal creation time. An attacker can borrow governance tokens via flash loan, vote with borrowed weight, then repay — all within one block.
  2. Timelock bypass: A proposal can be executed before the timelock delay has elapsed, or the timelock contract is controlled by an address that can be manipulated.
  3. Quorum manipulation: Quorum thresholds expressed as a fraction of totalSupply() or totalVotes() can be manipulated by flash-minting or burning governance tokens to reduce the effective quorum.
  4. Delegation exploits: Token delegation allows double-counting voting power or lets an attacker redelegate tokens to themselves before a vote snapshot.
  5. Treasury drain: Governance proposals that transfer protocol treasury funds without multi-sig confirmation, mandatory time delays, or spending limits.
  6. Emergency action abuse: Emergency functions that bypass governance controls (timelocks, quorum) can be triggered by a small number of signers, enabling rapid fund extraction without normal safeguards.

Sigvex also correlates governance findings with oracle data: when a contract has both oracle manipulation risk and governance control over the oracle configuration, the combined confidence is elevated to reflect the combo attack surface.

Why This Is an Issue

Governance attacks have produced some of DeFi’s largest single-transaction losses:

  • Beanstalk DAO (2022): $182M. An attacker used a flash loan to acquire a supermajority of governance tokens in a single transaction, passed an emergency proposal that transferred all protocol assets to their address, and repaid the flash loan — in one block. The protocol had no snapshot-based voting or timelock delay on emergency proposals.
  • Tornado Cash (2023): Governance takeover via a malicious proposal that granted the attacker 1.2M votes and full contract control. Emergency proposals lacked independent verification.
  • Audius (2022): $6M. A malicious proposal upgraded the staking contract and extracted treasury funds. The timelock was present but the upgrade proposal bypassed it through a delegatecall path.
  • Build Finance (2022): $470K. An attacker with a small amount of governance tokens passed a proposal uncontested during a low-participation period when quorum was easily reached with borrowed tokens.

How to Resolve

// Before: Vulnerable — voting weight from current balance (flash-loanable)
contract VulnerableGovernor {
    IGovernanceToken public token;

    function castVote(uint256 proposalId, bool support) external {
        // VULNERABLE: balance read at vote time, not snapshot time
        uint256 votes = token.balanceOf(msg.sender);
        _recordVote(proposalId, msg.sender, support, votes);
    }
}

// After: Use ERC-20 vote snapshots (ERC-5805)
contract SecureGovernor is Governor, GovernorVotes, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
        Governor("SecureDAO")
        GovernorVotes(_token)
        GovernorTimelockControl(_timelock)
    {}

    // Voting weight from the snapshot at proposal creation block, not current balance
    function _getVotes(address account, uint256 blockNumber, bytes memory)
        internal
        view
        override
        returns (uint256)
    {
        return token.getPastVotes(account, blockNumber); // Historical, not current
    }

    // Quorum: 4% of total supply at the proposal snapshot block
    function quorum(uint256 blockNumber) public pure override returns (uint256) {
        return token.getPastTotalSupply(blockNumber) * 4 / 100;
    }

    // Timelock delay applied to all proposals before execution
    function votingDelay() public pure override returns (uint256) { return 7200; }   // 1 day
    function votingPeriod() public pure override returns (uint256) { return 50400; } // 1 week
}

Examples

Vulnerable Code

contract VulnerableDAO {
    IERC20 public govToken;
    uint256 public constant QUORUM = 1_000_000e18;
    mapping(uint256 => Proposal) public proposals;

    struct Proposal {
        address target;
        bytes callData;
        uint256 yesVotes;
        uint256 noVotes;
        bool executed;
        uint256 createdAt;
    }

    function vote(uint256 proposalId, bool support) external {
        // VULNERABLE: current balance can be borrowed via flash loan
        uint256 weight = govToken.balanceOf(msg.sender);
        if (support) {
            proposals[proposalId].yesVotes += weight;
        } else {
            proposals[proposalId].noVotes += weight;
        }
    }

    function execute(uint256 proposalId) external {
        Proposal storage p = proposals[proposalId];
        require(!p.executed, "Already executed");
        require(p.yesVotes >= QUORUM, "Quorum not met");
        // VULNERABLE: no timelock delay — can be executed immediately after creation
        p.executed = true;
        (bool ok,) = p.target.call(p.callData);
        require(ok, "Execution failed");
    }
}

Fixed Code

import "@audited/governance/Governor.sol";
import "@audited/governance/extensions/GovernorVotes.sol";
import "@audited/governance/extensions/GovernorTimelockControl.sol";
import "@audited/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@audited/governance/extensions/GovernorSettings.sol";

contract SecureDAO is
    Governor,
    GovernorSettings,
    GovernorVotes,
    GovernorVotesQuorumFraction,
    GovernorTimelockControl
{
    constructor(IVotes _token, TimelockController _timelock)
        Governor("SecureDAO")
        GovernorSettings(
            7200,  // voting delay: 1 day in blocks
            50400, // voting period: 1 week
            100e18 // minimum proposal threshold
        )
        GovernorVotes(_token)
        GovernorVotesQuorumFraction(4) // 4% quorum
        GovernorTimelockControl(_timelock)
    {}

    // Voting weight is locked at the proposal snapshot block via EIP-5805 checkpoints
    // Flash loans cannot manipulate past balance checkpoints
}

Sample Sigvex Output

{
  "detector_id": "governance-attacks",
  "severity": "critical",
  "confidence": 0.75,
  "description": "Function castVote() reads governance token balance at vote time via token.balanceOf(msg.sender) rather than from a historical checkpoint. An attacker can use a flash loan to borrow governance tokens, cast a supermajority vote, and repay the loan within a single block. Additionally, execute() applies no timelock delay between proposal passage and execution.",
  "location": { "function": "castVote(uint256,bool)", "offset": 12 }
}

Detection Methodology

Sigvex identifies governance vulnerabilities through six targeted analysis passes:

  1. Flash loan voting detection: Identifies calls to balanceOf() (ERC-20) or getVotes() (ERC-5805) within voting functions and checks whether the token value is derived from current state (vulnerable) vs. a historical checkpoint via getPastVotes(account, blockNumber) (protected).
  2. Timelock bypass detection: Verifies that execution paths from proposal creation to proposal execution include a storage read or comparison against a timestamp/block-number delay variable. Proposals that can be created and executed in the same transaction are flagged.
  3. Quorum manipulation detection: Identifies quorum thresholds derived from live totalSupply() reads without a snapshot block parameter. Flash-mint attacks can manipulate live totalSupply().
  4. Delegation exploit detection: Checks for ERC-5805 delegate() calls that occur after proposal creation but before voting, which can redirect voting power without transferring tokens.
  5. Treasury drain detection: Flags governance proposals that call transfer(), transferFrom(), or send() on high-value assets without a separate multi-sig or spending limit check.
  6. Emergency action abuse: Detects functions marked as emergency or with restricted access control (Pausable, Guardian role) that bypass the standard proposal → timelock → execution flow.

Context modifiers:

  • OpenZeppelin Governor + GovernorTimelockControl detected: all confidence values multiplied by 0.3
  • Audited governance library present (OpenZeppelin): multiplied by 0.3
  • Pausable with access control present: emergency abuse confidence reduced

Limitations

False positives:

  • Off-chain governance systems (Snapshot + multisig execution) that use a custom on-chain executor may trigger flash-loan voting findings because the executor reads current state rather than past snapshots.
  • Protocols where the timelock is enforced off-chain (e.g., a guardian must sign execution) may be flagged for missing on-chain timelock even when the off-chain process is secure.

False negatives:

  • Cross-protocol governance attacks — where governance control over protocol A is used to manipulate protocol B — require cross-contract analysis beyond single-contract scope.
  • Social engineering attacks on multisig signers are outside static analysis scope.

References