Term Finance's vault DAOs had empty electorates, so $25 of deposits bought 100% of the vote in five vaults. The attacker self-approved proposals, zeroed a 7-day delay and drained 2,841.74 WETH and 1,679,639 USDC.
On August 23, 2026, an attacker took $8,466,497 from six Term Finance vaults on Ethereum. They bought most of each vault's governance token for a few dollars, then voted themselves control.
Every change to a Term vault goes through an Aragon OSx DAO. Its voting token is a wrapper you have to opt into: holding LP shares gives you no vote until you wrap them. Almost nobody ever did, because the DAO exists to veto changes and nobody had ever wanted to.
In the five USDC strategy vaults, nobody had wrapped anything at all. Total voting power was zero. On August 21 the attacker put $5 into each vault, wrapped the shares, and held 100.000% of the vote.
The ETH Meta Vault had one voter. A single holder had wrapped 0.05 shares on the day the DAO launched, sixteen months earlier. On August 17 the attacker bought 0.485216 shares for 0.5 ETH, wrapped them, and held 90.66%.
The proposals passed with the attacker as the only voter. Their first action switched off a seven-day delay. They then swapped each strategy's price oracle for a contract the attacker wrote. The vaults paid out 2,841.74 WETH and 1,679,639 USDC.
Term shut the meta vaults down the same day and revoked the DAO roles on six vaults. The ETH Meta Vault's share price has since been marked down 97.11%.
There is no contract bug in this incident. Term gave LP holders a veto over vault parameter changes, and nobody ever had cause to use it. The electorate stayed empty, so a majority cost the price of one share.
Each Term strategy vault is a Yearn V3 tokenized strategy holding fixed-term repo positions. A governor address controls its risk settings. That governor is a Gnosis Safe, reached through a Zodiac Roles modifier and a Zodiac Delay modifier. The one party allowed to drive that chain is an Aragon OSx DAO. The DAO has a single plugin, Aragon's TokenVoting, and its voting token is a GovernanceWrappedERC20 wrapped around the vault's LP share token. Holding LP shares on its own confers no vote.
The voting settings are the same in every vault:
supportThreshold 500000 // 50%, strictly greater than "no"
minParticipation 50000 // 5% of wrapped supply
minDuration 176400 // 49 hours (145 hours on the ETH Meta Vault)
minProposerVotingPower 0 // anyone may propose
votingMode 2 // early execution
minParticipation is measured against getPastTotalSupply() of the wrapper at the proposal's snapshot block, not against the vault's LP supply. If nobody has wrapped, 5% of nothing is nothing, and one wrapped share is both the quorum and the majority.
On August 21 at 05:26:47 UTC, the attacker's executor contract at 0x4f4b614d put 5 USDC into each of five Term USDC strategies and wrapped every share it got back. Those shares were the entire electorate in all five DAOs. Read getPastTotalSupply at each proposal's snapshot block and compare it to the attacker's own balance: the two numbers match every time. They are 503,572 units of tsvParityPrimeUSDC governance, 4,705,769 of tsvParityCoreUSDC, 4,928,513 of tsvParityHYUSDC, 4,957,092 of tsvParityHYUSDCv2 and 4,992,986 of tsvRockXToriUSDC. In each vault the attacker held 100.000% of the votes that existed. Every tally reads as the attacker's balance in "yes", against zero "no" and zero "abstain".
The ETH Meta Vault had one earlier voter. On April 1, 2025, the day the DAO went live, an address wrapped 0.05 tmvETH. For the next sixteen months that was the whole electorate. On August 17 the attacker bought 0.485216 tmvETH for 0.5 ETH in a cross-chain swap and wrapped it. Total voting power became 0.535216, and the attacker held 90.66%. The proposal snapshot at block 25772693 records exactly that.
An empty electorate looks like apathy, but these DAOs are veto gates, and a veto gate is built to sit idle.
Term's risk curators do not put parameter changes to a vote. They queue the change into the Zodiac Delay modifier, where it waits out the cooldown. For that same window a proposal titled "Veto strategy vault parameter change" is open in the DAO. Its single action calls setTxNonce on the Delay through the Roles modifier, and setTxNonce skips the queued transaction, which cancels the change. Pass the veto and the change dies. Ignore it and the change goes through.
Zero votes is therefore the system working as designed. The ETH Meta Vault DAO went live on April 1, 2025. Between August 2025 and June 2026 Term opened five veto proposals, and every one closed with zero yes, zero no and zero abstain, recorded as Rejected, so every parameter change went ahead. A depositor only had reason to wrap LP shares into the voting token if they wanted to stop something, and in sixteen months nobody did.
Sixteen months of vetoes nobody voted on, and the one proposal that passed. The attacker's is also the only one with no title.
The attacker's proposal is the untitled one. Term's five carry a description, because a veto is meant to be read before it is voted on. Proposal 5 supplied no metadata at all, so Aragon lists it as a bare TOKENVOTING-5, and it still executed.
The flaw is in what that pathway is allowed to carry. Each of Term's five veto proposals holds exactly one action, a callTargetFunctionWithRole wrapping a single setTxNonce. Nothing in the plugin restricts a proposal to that shape. TokenVoting holds general execute permission on the DAO, and the DAO can drive the Roles modifier into the Delay and the Safe behind it. The attacker's proposal carried seventeen actions through the same pathway, and the first three of them rewrote the Delay itself.
Proposal 5 is also the only proposal in this DAO's history to receive a single vote. Aragon records it as 0.49 GTMVETH in favour, nothing against, nothing abstaining, support at 100% and participation at 1,813.2% of the required minimum.
Every proposal starts with the same three actions, all of them aimed at the timelock rather than the money:
// 1. neutralise the Zodiac Delay modifier through the Roles modifier
Roles.callTargetFunctionWithRole(delay, abi.encodeCall(Delay.setTxCooldown, (0)), 1);
Roles.callTargetFunctionWithRole(delay, abi.encodeCall(Delay.setTxExpiration, (0)), 1);
// 2. make the DAO itself a module on the Delay, so it can queue and execute
Roles.callTargetFunctionWithRole(delay, abi.encodeCall(Delay.enableModule, (dao)), 1);Before the attack, txCooldown on the ETH Meta Vault's Delay modifier was 608,400 seconds. That is a 7.04 day wait between queuing a privileged call and running it, and it is what would have given depositors a week to get out. But it is only a parameter, and governance owns it, so the first action of the drain set it to zero. After that, every action pairs execTransactionFromModule with executeNextTx, queuing a call and running it on the very next line, because the cooldown it would have waited for is gone.
From here the two transactions differ: the ETH vault and the USDC vaults were drained by different mechanisms.
Transaction 0xd354a15b runs proposal 5 against the ETH Meta Vault at 0x26fcb50e, a Yearn V3 vault that spreads WETH across four Term ETH strategies. The proposal calls all of it back, then sends it out again in one move:
// pull every legitimate strategy back to zero debt, accepting 100% loss tolerance
vault.update_debt(SHOREWOODS_ETH, 0, 10_000);
vault.update_debt(AUGUST_DIGITAL_ETH, 0, 10_000);
vault.update_debt(PARITY_PRIME_ETH, 0, 10_000);
vault.update_debt(PARITY_CORE_ETH, 0, 10_000);
// install the attacker's own strategy and give it unlimited debt
vault.add_strategy(EXIT_STRATEGY, false);
vault.update_max_debt_for_strategy(EXIT_STRATEGY, type(uint256).max);
vault.update_debt(EXIT_STRATEGY, type(uint256).max, 10_000);The recall reaches down into the money markets underneath. Shorewoods ETH and August Digital ETH unwind Aave V3 positions. Parity Prime ETH and Parity Core ETH redeem MetaMorpho vault shares. That returns 44, 44, 1,446 and 1,308 WETH into the meta vault. The last update_debt then pushes all 2,841.74 WETH into 0x184f2e57, a contract the attacker had deployed six days earlier and named Fixed Recipient WETH Exit Strategy. The contract satisfies the ERC-4626 interface the vault checks for: it accepts the asset and mints shares back. It then forwards the WETH straight out, logging an AssetsForwarded event on the way.
That left the meta vault holding 2,841.74 shares of a strategy that held no WETH at all, while its pricePerShare kept reporting 1.030751 for the rest of the day.
This is the transaction Defimon's feed fired on, as a suspicious_large_transfer with attacker profit of $6,746,447. The alert named Morpho as the victim rather than Term Finance. The biggest single balance change in the transaction, $6,536,931, lands on Morpho's singleton at 0xbbbbbbbb, because that is where Parity Prime ETH and Parity Core ETH pulled 3,431,504 and 3,105,017 dollars of liquidity on their way out. Term Finance shows up one line below, under involved protocols, next to Aave V3. Nothing was wrong with Morpho, and Morpho's own depositors lost nothing. The Term vaults used a withdrawal right they genuinely held, on the orders of a governor who had bought the job for five dollars.
Transaction 0x9f273f9a runs five proposals in one call and uses Term's own accounting instead. After switching off each Delay modifier, every proposal runs the same steps against its strategy:
strategy.setPendingGovernor(dao);
strategy.acceptGovernor(); // DAO is now the governor
strategy.setTermController(attacker); // attacker defines what a repo token is
strategy.setDiscountRateAdapter(attacker); // attacker defines what it is worth
strategy.setRequiredReserveRatio(0); // no reserve need be kept
strategy.setRepoTokenConcentrationLimit(type(uint256).max); // no diversification limit
USDC.approve(attacker, type(uint256).max);
strategy.sellRepoToken(attacker, 1_000_000); // strategy buys the attacker's token
USDC.approve(attacker, 0);Term strategies buy repo tokens from sellers. To price one, a strategy asks a termController whether the token is genuine, and a discountRateAdapter what discount rate applies. The proposal points both at 0x7fd5a986, a single contract the attacker wrote, then sells that same contract to the strategy as a repo token. So the strategy checks the token with the attacker, prices it with the attacker, and pays real USDC for it. This is oracle manipulation without any price manipulation: governance replaced the contract that reports the price.
Setting requiredReserveRatio to zero is what makes the payout total instead of partial. The reserve ratio is the share of assets a strategy has to keep liquid, and at zero it can spend its whole liquid balance on one purchase. Each strategy first cashes out its MetaMorpho position to raise the money, then hands it over: 14,132 USDC from Parity Prime USDC, 14,172 from Parity Core USDC, 348,877 from Parity High Yield USDC, 848,411 from Parity High Yield USDC v2 and 454,046 from RockawayX Tori USDC. That comes to 1,679,639 USDC.
There is no flash loan in either transaction, and none was needed. The attacker spent about 2 ETH in total, taken out of Tornado Cash as two 1 ETH notes. Of that, 0.5 ETH and 25 USDC bought governance and the rest paid for gas.
All times are UTC. Both operator addresses were funded from the same Tornado Cash pool a day apart, and both ran on the same morning.
The realized loss is 2,841.743536 WETH and 1,679,639.290442 USDC. Priced at the Chainlink ETH/USD rate the feed carried at the attack block, $2,388.27, that is $6,786,858 and $1,679,639, or $8,466,497 in total. Both amounts were swapped and gathered into one externally owned account at 0xd5183d8b. It holds 2,843 ETH and 1,679,642 DAI as of writing, and none of it has moved.
Six vaults lost their liquid balance. The ETH Meta Vault lost all 2,841.74 WETH. That money had come from four Term ETH strategies, and beneath those, from the Aave V3 and MetaMorpho positions the proposal unwound on its way through. Losses across the USDC strategies were uneven, and each lost roughly what it happened to hold in liquid form: Parity High Yield USDC v2 lost 848,411 USDC, RockawayX Tori USDC 454,046, Parity High Yield USDC 348,877, Parity Core USDC 14,172 and Parity Prime USDC 14,132.
For most of August 23 the vaults did not show the loss. The attacker's contract sat in each strategy's repoTokenHoldings list, and the same contract was the discountRateAdapter that priced it. The ETH Meta Vault reported 2,926 WETH of assets and a price per share of 1.030751 while holding no WETH at all, since its whole balance sheet was 2,841.74 shares of a contract that also held none. Term fixed that late the same day by calling force_revoke_strategy on the malicious strategy, which wrote the position off as a loss. Price per share fell from 1.030751 to 0.029756, a drop of 97.11%, and reported assets fell to 84.472348 WETH. Anyone who checked the share price during that window saw a figure the vault could not back.
The USDC strategies are only partly re-marked. Parity Core USDC is down to 2.02 USDC of reported asset value and Parity High Yield USDC to 189,777. But Parity Prime USDC still reports 33,685, RockawayX Tori USDC 47,904 and Parity High Yield USDC v2 2,444,842, and all five still list the attacker's contract among their repo token holdings with that same contract set as their discount rate adapter. Those figures are not recoverable value while the attacker's contract still prices them.
The attacker got governance by depositing at fair value, not by pushing a price around. So no other depositor was diluted on the way in, and nothing about the purchase looked odd. Five USDC deposits and a 0.5 ETH swap look exactly like a small retail user trying a protocol for the first time.
In an update on August 24, Term Labs said all Term Meta Vaults have been shut down and DAO governance roles revoked. The shutdown cannot be undone and permanently blocks new deposits, while withdrawals stay open. It said the underlying Term protocol and its direct borrowing and lending markets appear unaffected, with checks still under way, and that the team is working with outside security teams on remediation and recovery and will look at ways to cover any shortfall left over. It asked people to trust only its own account and to watch out for impersonators.
The shutdown checks out on-chain. shutdown_vault() was queued against the ETH Meta Vault at 07:52 UTC on August 23, and the vault now returns true from isShutdown() and zero from maxDeposit, which in Yearn V3 is a one-way state. Withdrawals are open in the sense that nothing blocks the redemption path, but you can only take out what the vault can free up. It holds no idle WETH, and all 84.472348 WETH of its remaining assets sit as debt in four ETH strategies, two of them the ones still under contested governance. Someone holding 10 tmvETH has a position worth about 0.2976 WETH at the new share price, and can currently redeem about 0.006 WETH of it.
The revocation checks out too, but only on six of the eight vaults. Term ran assignRoles against the Zodiac Roles modifier on six vaults on August 23, between 09:04 and 23:05 UTC, and on all six the Delay queue is now empty and the DAO's role is gone. Term could move that fast because the attacker had already set txCooldown to zero on those six as the opening move of the drain, so Term's own fix inherited a timelock the attacker had removed. On the two ETH vaults the attacker did not drain, the original 262,800 second cooldown is intact, so Term's revocation there is still queued and waiting. The protocol's own timelock is protecting the attacker's position on exactly the two vaults that still hold money.
Depositors handled the other half of the response themselves, by wrapping LP shares into governance tokens to fight votes that had already been counted. It is the only option open to anyone who is not the governor, and it works off a snapshot taken when a proposal is created, so it cannot touch a proposal that already exists. It has pushed the attacker below a majority in two of the five USDC DAOs, and it cannot reach the two ETH proposals at all, because voting there closed hours before the wrapping began.
None of these contracts needs patching. An opt-in electorate that nobody joins leaves a vault under the control of whoever turns up first, and the settings that look like they prevent that, quorum and delay and support threshold, belong to whoever wins the first vote. Three settings would each have blocked the attack on their own. A non-zero minProposerVotingPower would have stopped an account holding five dollars from opening a proposal. A minParticipation measured against LP supply instead of wrapped supply would have made an empty electorate fail quorum instead of passing it easily. And a delay that governance cannot shorten, held by a separate key or a fixed contract, would have turned the drain into a seven-day public warning.
The proposals sat on-chain and readable for six days, the first drain was flagged as it landed at 06:25 UTC, and 22 minutes passed before the second one took the USDC vaults. Nobody was in a position to act during that gap. A machine-readable exploit feed turns that gap into an automatic withdrawal or a governance veto instead of a post-mortem, but only for teams who have wired one to something that can move. This incident fits the 2026 loss record, in which governance and key control, not contract bugs, account for most of the money.
Defimon detects exploits on major chains the moment they execute and streams them to you: human-readable alerts in Telegram, or structured JSON over WebSocket for your own systems.
// signals $50/mo · websocket $200/mo · channel free
@DefimonAlerts