Xave DaoModule Takeover
Exploit Transactions
Victim Addresses
0x8f9036732b9aa9b82d8f35e54b71faeb2f573e2fEthereum0x7eaE370E6a76407C3955A2f0BBCA853C38e6454EEthereum0xe94b97b6b43639e238c851a7e693f50033efd75cEthereum0x6335A2E4a2E304401fcA4Fc0deafF066B813D055Ethereum0x579270F151D142eb8BdC081043a983307Aa15786EthereumLoss Breakdown
Similar Incidents
BUILD Governance Takeover and Unlimited Mint ACT Exploit
34%CEXISWAP Proxy Takeover
33%Beanstalk flash-loan governance takeover drains treasury assets
31%DeRace vesting proxy ownership takeover and emergency exit
30%LiteV3 Bridge Aggregator Proxy Initialization Race Enabled Unauthorized UUPS Takeover
30%Audius Governance Reinitialization and Treasury AUDIO Drain
30%Root Cause Analysis
Xave DaoModule Takeover
1. Incident Overview TL;DR
An unprivileged Ethereum account exploited Xave Finance's governance module configuration to seize control of Safe-managed protocol assets. In transaction 0x71e2e18665d16bb2064bff4ae4c6e5a8bf366721e511242d403ad9aca5b138fa, the attacker created an arbitrary DaoModule proposal and self-answered the linked Realitio question. In transaction 0xc18ec2eb7d41638d9982281e766945d0428aaeda6211b4ccb6626ea7cff31f4a, the attacker waited for the one-second timeout to elapse and then executed four Safe module transactions that minted 100000000000000000000000000000000 HALO and transferred ownership of HaloToken, 0x6335A2E4a2E304401fcA4Fc0deafF066B813D055, and PrimaryBridge.
The root cause was a permissionless proposal path on DaoModule combined with production parameters that made oracle approval effectively trivial: questionTimeout=1, questionCooldown=0, and minimumBond=0. That combination let any caller turn the enabled DaoModule into a public Safe executor.
2. Key Background
Xave's DaoModule is attached to the Safe proxy at 0x7eaE370E6a76407C3955A2f0BBCA853C38e6454E. DaoModule stores a proposal as a Realitio question built from proposalId and a list of transaction hashes; later, executeProposalWithIndex checks the recorded question, verifies a positive oracle result, and forwards the chosen call to the Safe.
The Safe implementation matters because execTransactionFromModule is a privileged path for enabled modules. It bypasses owner signatures and only checks that msg.sender is an enabled module:
function execTransactionFromModule(address to, uint256 value, bytes memory data, Enum.Operation operation)
public
returns (bool success)
{
require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "Method can only be called from an enabled module");
success = execute(to, value, data, operation, gasleft());
}
The asset-side impact is straightforward. HaloToken is Ownable, and its mint function is gated only by onlyOwner and canMint:
function mint(address account, uint256 amount) external onlyOwner {
require(canMint == true, "Total supply is now capped, cannot mint more");
_mint(account, amount);
}
Before exploitation, the Safe owned HaloToken, the unnamed ownable component at 0x6335A2E4a2E304401fcA4Fc0deafF066B813D055, and PrimaryBridge. Once the attacker could make the Safe execute arbitrary calls through DaoModule, minting and ownership seizure followed directly.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is an access-control failure in governance execution, not a bug in Safe itself. DaoModule exposes addProposal and addProposalWithNonce publicly, so any caller can register a proposal and create the corresponding Realitio question. On the live deployment used in the incident, the module also accepted a one-second question timeout, zero additional cooldown, and zero minimum answer bond. Those settings reduced proposal approval to a self-submitted answer=1 on Realitio plus a short wait. After that, executeProposalWithIndex only required that the proposal hash match, the oracle answer be positive, and the previous indexed transaction already be executed. Because DaoModule was an enabled Safe module, each validated proposal step was forwarded to execTransactionFromModule, which performs no owner-signature verification. The resulting system invariant failure was that governance approval no longer represented privileged consensus; it became a permissionless path to arbitrary Safe execution.
4. Detailed Root Cause Analysis
The decisive breakpoint is in DaoModule itself. addProposal simply delegates to addProposalWithNonce, and addProposalWithNonce does not restrict who may call it:
function addProposal(string memory proposalId, bytes32[] memory txHashes) public {
addProposalWithNonce(proposalId, txHashes, 0);
}
function addProposalWithNonce(string memory proposalId, bytes32[] memory txHashes, uint256 nonce) public {
string memory question = buildQuestion(proposalId, txHashes);
bytes32 questionHash = keccak256(bytes(question));
...
questionIds[questionHash] = expectedQuestionId;
bytes32 questionId = oracle.askQuestion(templateId, question, arbitrator, timeout, 0, nonce);
require(expectedQuestionId == questionId, "Unexpected question id");
}
The execution gate is similarly weak under the live configuration:
function executeProposalWithIndex(...) public {
bytes32 questionId = questionIds[questionHash];
require(questionId != bytes32(0), "No question id set for provided proposal");
require(oracle.resultFor(questionId) == bytes32(uint256(1)), "Transaction was not approved");
require(minimumBond == 0 || minimumBond <= oracle.getBond(questionId), "Bond on question not high enough");
require(finalizeTs + uint256(questionCooldown) < block.timestamp, "Wait for additional cooldown");
require(executor.execTransactionFromModule(to, value, data, operation), "Module transaction failed");
}
At block 15704736, the deployed DaoModule had questionTimeout=1, questionCooldown=0, minimumBond=0, and answerExpiration=604800. That means an unprivileged caller could create any proposal, answer it positively with a 1 wei bond, wait slightly more than one second, and then execute it through the Safe.
The trace for the first adversary transaction shows exactly that sequence:
DaoModule::addProposalWithNonce("2", [...], 0)
Realitio::submitAnswer{value: 1}(..., 0x...01, 0)
The second adversary transaction shows DaoModule forwarding the attacker-selected bundle through the Safe module path:
0x8f9036732b9aa9b82D8F35e54B71faeb2f573E2F::executeProposalWithIndex("2", [...], 0xE94B97..., 0, 0x40c10f19..., 0, 0)
GnosisSafe::execTransactionFromModule(0xE94B97..., 0, 0x40c10f19..., 0)
0xE94B97...::mint(0x0f44f3489d17e42ab13a6beb76e57813081fc1e2, 100000000000000000000000000000000)
The same execution transaction then transferred ownership of all three victim-owned contracts to the attacker via transferOwnership(address). The collected HALO balance diff confirms the semantic effect: the attacker EOA's HALO balance increased from 0 to 100000000000000000000000000000000. This is why the correct invariant is: only governance-authorized proposal bundles should ever reach execTransactionFromModule, and that invariant failed because proposal creation was public while approval parameters were effectively permissionless.
5. Adversary Flow Analysis
The adversary flow was a two-transaction takeover.
-
0x71e2e18665d16bb2064bff4ae4c6e5a8bf366721e511242d403ad9aca5b138faat block15704737The attacker EOA0x0f44f3489d17e42ab13a6beb76e57813081fc1e2deployed helper contract0xe167cdaac8718b90c03cf2cb75dc976e24ee86d3, computed four DaoModule transaction hashes, calledaddProposalWithNonce("2", txHashes, 0), and then submitted a positive Realitio answer with bond1. -
0xc18ec2eb7d41638d9982281e766945d0428aaeda6211b4ccb6626ea7cff31f4aat block15704746After the timeout elapsed, the attacker calledexecuteProposalWithIndexfour times in sequence. The four executed calls were:HaloToken.mint(attacker, 100000000000000000000000000000000),HaloToken.transferOwnership(attacker),0x6335A2E4a2E304401fcA4Fc0deafF066B813D055.transferOwnership(attacker), andPrimaryBridge.transferOwnership(attacker).
The execution trace captures that sequence directly:
...::executeProposalWithIndex("2", [...], 0xE94B97..., 0, 0x40c10f19..., 0, 0)
0xE94B97...::mint(attacker, 100000000000000000000000000000000)
...::executeProposalWithIndex("2", [...], 0xE94B97..., 0, 0xf2fde38b..., 0, 1)
0xE94B97...::transferOwnership(attacker)
...::executeProposalWithIndex("2", [...], 0x6335A2..., 0, 0xf2fde38b..., 0, 2)
0x6335A2...::transferOwnership(attacker)
...::executeProposalWithIndex("2", [...], 0x579270..., 0, 0xf2fde38b..., 0, 3)
PrimaryBridge::transferOwnership(attacker)
No privileged Safe owner signatures, private keys, or attacker-only artifacts were needed. The only prerequisites were public contract interfaces, the enabled DaoModule, and the permissive Realitio settings present at the exploit block.
6. Impact & Losses
The on-chain loss artifact shows an unauthorized mint of HALO to the attacker:
{
"token": "0xe94b97b6b43639e238c851a7e693f50033efd75c",
"holder": "0x0f44f3489d17e42ab13a6beb76e57813081fc1e2",
"before": "0",
"after": "100000000000000000000000000000000",
"delta": "100000000000000000000000000000000"
}
The incident also transferred ownership of HaloToken, the unnamed ownable component at 0x6335A2E4a2E304401fcA4Fc0deafF066B813D055, and PrimaryBridge from the Safe to the attacker. The measured token loss in the collected artifacts is:
- HALO:
100000000000000000000000000000000raw units,decimal=18
Because the attacker gained protocol ownership roles in addition to the token mint, the impact was broader than a single transfer: the Safe's governance boundary over token supply control and bridge-related administration was broken.
7. References
- DaoModule source:
0x8f9036732b9aa9b82d8f35e54b71faeb2f573e2f - Gnosis Safe implementation source:
0x34cfac646f301356faa8b21e94227e3583fe3f5f - HaloToken source:
0xe94b97b6b43639e238c851a7e693f50033efd75c - Adversary tx
0x71e2e18665d16bb2064bff4ae4c6e5a8bf366721e511242d403ad9aca5b138fatrace and receipt - Execution tx
0xc18ec2eb7d41638d9982281e766945d0428aaeda6211b4ccb6626ea7cff31f4atrace and balance diff - Auditor oracle and validator execution log confirming the same semantic end state