Workspace IndexDev Notes › Governance capture — when the exploit is a market order

#1PoC

Governance capture — when the exploit is a market order

$8.5M left Term's vaults without a line of code breaking. The number nobody publishes for a token-governed vault is what decisive voting power costs against what it controls.

Mechanism, denominators and the failure of the existing defences are documented in this card as of 2026-08-25. What remains is arithmetic on live protocols and belongs in a follow-up: pull historical turnout from Snapshot or Tally, price decisive voting weight against DEX depth, and set it against governed TVL. The invariant is time-varying — likely satisfied at launch and inverted silently as the token declined — so the deliverable is a monitor rather than an audit. Source: the Term Labs governance exploit, 2026-08-23. No official postmortem published, so the exact permission path is inference.

Why

The distinction is the whole story. Term has two very different products under one brand: the core fixed-rate repo markets use per-loan collateral lockers to isolate borrower and lender exposure, and that design held and was untouched. What drained was Term Vaults, a separate yield product built as Yearn v3 contracts, and Yearn was explicit that this was not their bug — the attack came through Term's own governance wrapper sitting on top of the vault. Yearn V3 is deliberately un-opinionated: it exposes privileged roles (role_manager, debt manager, emergency manager) and says nothing whatsoever about who holds them. That holder can be an EOA, a multi-sig, or a governance contract that relays calls, and Term put a DAO vote there. So the audited surface ends at the vault, and the actual security of the vault becomes a property of the token distribution, which no auditor reviews. The invariant that should govern any token-controlled vault is that the cost to acquire decisive voting power exceeds the value that power controls. Term's vault TVL was $12.2M, $8.6M of it on Ethereum, against a governance token cheap and thin enough to corner from a 2 ETH base routed through Tornado Cash. When that inequality inverts this stops being an exploit and becomes an arbitrage — it needs no bug, no zero-day and no cleverness, it executes deterministically, and the attacker is racing nobody. The seed size matters more than it looks: this was not a Beanstalk-style flash-loan attack renting voting power for one block, because the float was thin enough to buy outright, which also means there was no anomalous funding signature to alert on. Accumulation looks like organic buying right up until the proposal executes. This catalogue has recorded the same shape twice already — the stake-concentration card, where a set looks like N independent units while the unit of independence is smaller, and the Aqua card, where quoted depth is real but only for whoever arrives first. Here it is N tokens that are not N voters.

How it works

Mechanics as reported: the attacker cornered a majority of a sparsely held governance token, then passed malicious proposals to seize the strategy vaults, holding 100% of voting power in four of five USDC strategy vaults and roughly 91% in the Ethereum Meta Vault at execution time. The vault contracts then executed the malicious calls as legitimate, because they were. Execution was not a naive transfer either — the first exploit transaction burned 44.37 aEthWETH and withdrew WETH from Aave through the stataEthWETH wrapper, meaning the proposal forced the strategy to unwind external positions first and then redirect. From the vault state machine's point of view that is an entirely ordinary rebalance-and-shutdown sequence, no invariant was violated, and no monitor watching for impossible state would have fired. The exit was 2,843 ETH and about 1.68M USDC swapped to DAI, consolidated into one wallet beginning 0xD5183, and the USDC-to-DAI hop is a read on which assets carry a centralized freeze function. Term reportedly had a seven-day timelock plus an LP veto and neither stopped it, which is the part worth building the measurement around. A timelock is latency, not a control: it converts a security property into an operational one, requiring someone to watch the proposal queue, recognise a hostile payload, and hold both the authority and the liveness to act inside the window. If nobody runs that loop the delay only means the theft is scheduled a week in advance — and notably the incident was first surfaced by a third party's on-chain monitoring bot rather than by Term or its LPs. A veto held by an apathetic quorum is likewise not exercisable, since the population that let voting power reach 100% is the same population expected to veto. So the PoC is two numbers per protocol and neither needs a contract deployed. First, the naive cost of buying 51% of supply against the real cost of buying 51% of historical turnout, because quorum measured against turnout rather than total supply degrades to whoever shows up; the gap between those two figures is the number nobody publishes, and it is where governance apathy stops being a governance problem and becomes a direct reduction in attack cost. Second, and cheaper: for every protocol advertising a timelock, has any proposal ever actually been cancelled or vetoed? If the answer is never, the timelock is decorative, and its presence on a security page is a claim about a loop nobody runs. Context for scale: DefiLlama classified five governance attacks in 2026 totalling $25.1M, led by a $20M malicious proposal against BonkDAO in July, and Term itself lost roughly $1.5M in May 2025 to an oracle decimal mismatch during a routine upgrade — a different failure with the same underlying theme, that the risk kept landing in the configuration and privilege layer rather than in the core math.

Update 2026-08-25 — mechanism worked through, card closed

What a DAO proposal actually is. Not a policy document but the transaction itself. A Governor exposes propose(address[] targets, uint256[] values, bytes[] calldatas, string description), and execution is a loop of targets[i].call{value: values[i]}(calldatas[i]). The governance contract is a machine that makes arbitrary calls on request, and the vote is its trigger. The description field is not enforced on-chain, so a proposal may read "Q3 strategy rebalance" while the calldata does something else entirely; only the calldata binds.

So the attacker's actions reduce to two: author a transaction, which anyone may do, and win the vote, which requires buying tokens. No privilege escalation, no reentrancy, no overflow. The DAO was used exactly as designed.

What sat in the calldata. Every Yearn v3 function involved is an ordinary operational one — set_role, add_strategy, update_debt, shutdown_vault. The reported execution burned 44.37 aEthWETH and withdrew WETH from Aave through the stataEthWETH wrapper, which is the on-chain signature of update_debt(aaveStrategy, 0): unwind the external position back into the vault first, then redirect. Inferred sequence, since no postmortem is published: register an attacker contract as a strategy, zero the Aave strategy's target debt to recall funds, push the recalled balance into the attacker strategy, withdraw. A legitimate rebalance and this attack differ by one twenty-byte address in a second argument. Nothing monitoring for invariant violations fires, because no invariant is violated.

Three denominators, and only the third is the attack cost. Cost to buy 51% of total supply is the number people quote; cost to buy 51% of the liquid float is closer; cost to buy 51% of historical turnout is what actually had to be paid. Locked team and treasury allocations do not vote, and most circulating holders do not either, so an attacker holding a small single-digit percentage of supply can hold 100% of votes cast — which is what the reported figures describe. A quorum floor measured against total supply would have raised the cost by orders of magnitude, but quorum floors set high enough to matter also paralyse DAOs, so protocols relax them, and the relaxation is the discount.

Why the existing defences did not bind. A seven-day timelock is latency, not a control: it converts a security property into an operational one requiring somebody to watch the proposal queue, recognise a hostile payload, hold authority and remain live inside the window. Nobody ran that loop — the incident was surfaced by a third party's monitoring bot rather than by Term or its LPs. An LP veto held by an apathetic quorum is likewise not exercisable, since the population that let voting power reach 100% is the population expected to exercise it. And the absence of a flash loan made things worse rather than better: Beanstalk-style rental leaves an enormous funding signature, while a float thin enough to buy outright makes accumulation indistinguishable from organic demand until execution.

The fixes, ordered by whether they need a human. Narrowing the executor to a whitelist of pre-registered functions and addresses would have made add_strategy(attacker) unreachable, and it works with nobody watching. Tying voting weight to vault shares rather than to a separate token makes the invariant self-enforcing: controlling 51% requires depositing 51%, so half of anything stolen was already the attacker's. A guardian key independent of the token can cancel during the timelock. A longer timelock alone changes nothing, since the loop it depends on was never running.

Why this keeps recurring. After Beanstalk the industry adopted vote snapshots, voting delays and timelocks, which defeat flash-loan governance attacks completely — and Term passed all of them by simply buying and holding. The vector was patched, not the class. Meanwhile the configuration itself is the industry default, because placing role_manager in a multisig invites the criticism of centralisation, and narrowing governance powers reduces token utility, lowers the price, and thereby cheapens the remaining voting power. Security and the decentralisation narrative pull in opposite directions, which is why no team's org chart contains the person who would compute this ratio.

Closing status. The mechanism, the denominators, the failure of timelock and veto, and the ordered fixes are now documented here. What remains is arithmetic on live protocols, and it belongs in a follow-up rather than in scoping: pull historical turnout from Snapshot or Tally, price decisive weight against DEX depth, and set it against governed TVL. Note also that the invariant is time-varying — it was likely satisfied at launch and inverted silently as the token declined — so the deliverable is a monitor rather than an audit.

← All Dev Notes · Workspace Index · Top ↑

거버넌스 장악 — 익스플로잇이 그냥 시장가 매수일 때

코드는 한 줄도 깨지지 않았는데 Term의 볼트에서 850만 달러가 빠져나갔습니다. 토큰이 지배하는 볼트에 대해 아무도 발표하지 않는 숫자는, 결정권을 쥘 만큼의 표를 사는 값이 그 표가 통제하는 금액 대비 얼마인가입니다.

메커니즘·세 분모·기존 방어(타임락·LP 거부권)의 실패 이유까지 2026-08-25에 이 카드에 정리 완료. 남은 것은 살아 있는 프로토콜에 대한 산수이고 후속 작업으로 분리한다: Snapshot·Tally에서 과거 실투표량을 긁고, DEX 깊이에 대해 결정적 가중치의 가격을 매기고, 거버넌스가 통제하는 TVL에 대본다. 이 불변식은 시간에 따라 변한다 — 출시 시점엔 만족했다가 토큰 하락과 함께 조용히 뒤집혔을 것이므로, 산출물은 감사가 아니라 모니터여야 한다. 출처: Term Labs 거버넌스 익스플로잇(2026-08-23). 공식 포스트모템이 없어 정확한 권한 경로는 추론.

구분이 곧 이야기 전부입니다. Term은 한 브랜드 아래 성격이 완전히 다른 제품 둘을 갖고 있습니다 — 핵심 고정금리 레포 시장은 대출 건별 담보 락커로 차입자·대여자 익스포저를 격리하고, 그 설계는 멀쩡했고 손도 안 탔습니다. 털린 것은 Term Vaults, Yearn v3 컨트랙트로 지은 별도 수익 상품입니다. 그리고 Yearn은 자기네 버그가 아니라고 분명히 했습니다 — 공격은 볼트 위에 얹힌 Term 자신의 거버넌스 래퍼를 통해 들어왔으니까요. Yearn V3는 의도적으로 무입장(un-opinionated)입니다: 특권 역할(role_manager·debt manager·emergency manager)을 노출하되 그것을 누가 쥐는지에 대해서는 아무 말도 하지 않습니다. EOA일 수도, 멀티시그일 수도, 호출을 중계하는 거버넌스 컨트랙트일 수도 있고 — Term은 거기에 DAO 투표를 놓았습니다. 그래서 감사받은 표면은 볼트에서 끝나고, 볼트의 실제 보안은 토큰 분포의 성질이 됩니다. 그리고 토큰 분포를 감사하는 사람은 없습니다. 토큰이 지배하는 볼트를 규율해야 할 불변식은 하나입니다 — 결정권을 쥘 만큼의 표를 사는 비용 > 그 표가 통제하는 가치. Term의 볼트 TVL은 1,220만 달러(그중 이더리움에 860만)였고, 상대편에는 Tornado Cash를 거친 2 ETH로 매집을 시작할 만큼 얇고 싼 거버넌스 토큰이 있었습니다. 이 부등식이 뒤집히는 순간 그것은 익스플로잇이 아니라 차익거래가 됩니다 — 버그도 제로데이도 영리함도 필요 없고, 결정론적으로 실행되며, 공격자는 아무와도 경주하지 않습니다. 종잣돈 크기가 보이는 것보다 중요합니다: 이것은 한 블록 동안 표를 빌리는 Beanstalk식 플래시론 공격이 아니었습니다. 유통량이 그냥 사버릴 만큼 얇았기 때문이고, 그 말은 곧 경보를 울릴 이상 자금 신호도 없었다는 뜻입니다. 매집은 제안이 실행되는 순간까지 평범한 매수처럼 보입니다. 이 카탈로그는 같은 형태를 이미 두 번 기록했습니다 — stake-concentration(겉보기엔 독립 단위 N개인데 실제 독립성의 단위는 더 작다)과 Aqua(호가된 깊이는 실재하지만 먼저 도착한 사람에게만). 여기서는 N개의 토큰이 N명의 투표자가 아니라는 것입니다.

동작 방식

보도된 동작 방식: 공격자는 보유가 희박한 거버넌스 토큰의 과반을 매집한 뒤 악성 제안을 통과시켜 전략 볼트를 장악했고, 실행 시점에 USDC 전략 볼트 5개 중 4개에서 의결권 100%, 이더리움 메타 볼트에서 약 91%를 쥐고 있었습니다. 그러자 볼트 컨트랙트는 악성 호출을 정당한 것으로 실행했습니다 — 실제로 정당했으니까요. 실행도 단순 transfer가 아니었습니다. 첫 익스플로잇 트랜잭션은 44.37 aEthWETH를 소각하고 stataEthWETH 래퍼를 통해 Aave에서 WETH를 인출했습니다 — 즉 제안이 전략에게 외부 포지션을 먼저 청산한 뒤 방향을 돌리게 시킨 겁니다. 볼트 상태 머신의 관점에서 이것은 지극히 평범한 리밸런스·셧다운 시퀀스이고, 불변식은 하나도 위반되지 않았으며, "불가능한 상태"를 감시하는 모니터는 아무것도 울리지 않았을 것입니다. 탈출은 2,843 ETH와 약 168만 USDC(→DAI로 스왑), 전부 0xD5183으로 시작하는 지갑 하나로 모였고 — USDC→DAI 한 단계는 어느 자산에 중앙화된 동결 기능이 있는지를 읽은 것입니다. Term에는 7일 타임락과 LP 거부권이 있었다고 하는데 둘 다 막지 못했고, 측정을 설계할 값어치가 있는 부분이 바로 여기입니다. 타임락은 통제가 아니라 지연입니다. 보안 속성을 운영 속성으로 바꿔놓을 뿐이라, 누군가 제안 큐를 지켜보고, 페이로드가 적대적임을 알아보고, 창 안에서 행동할 권한과 가용성을 모두 갖고 있어야 합니다. 그 루프를 아무도 돌리지 않으면 지연은 도난이 일주일 전에 예약돼 있었다는 뜻밖에 안 됩니다 — 그리고 이 사건은 Term도 LP도 아닌 제3자의 온체인 감시 봇이 먼저 발견했습니다. 무관심한 정족수가 쥔 거부권도 행사되지 않습니다 — 의결권이 100%까지 가도록 방치한 집단과 거부권을 행사할 것으로 기대되는 집단이 같은 집단이니까요. 그래서 PoC는 프로토콜당 숫자 둘이고, 둘 다 컨트랙트를 배포할 필요가 없습니다. 첫째, 총발행량의 51%를 사는 순진한 비용 대 과거 실제 투표율의 51%를 사는 진짜 비용. 정족수를 총발행량이 아니라 투표율 기준으로 재면 결국 "나온 사람들" 기준으로 퇴화하기 때문이고, 그 두 숫자의 간격이 아무도 발표하지 않는 그 숫자이며, 거기서 거버넌스 무관심은 거버넌스 문제이기를 그만두고 공격 비용의 직접적 인하가 됩니다. 둘째, 더 싼 것: 타임락을 내세우는 모든 프로토콜에 대해 — 제안이 실제로 취소되거나 거부된 적이 한 번이라도 있는가? 답이 "한 번도 없다"면 그 타임락은 장식이고, 보안 페이지에 적힌 그 줄은 아무도 돌리지 않는 루프에 대한 주장입니다. 규모의 맥락: DefiLlama는 2026년 거버넌스 공격 5건, 합계 2,510만 달러로 분류했고 그중 최대는 7월 BonkDAO에 대한 2,000만 달러 악성 제안이었습니다. 그리고 Term 자신도 2025년 5월 정기 업그레이드 중 오라클 소수점 불일치로 약 150만 달러를 잃었습니다 — 다른 실패, 같은 주제입니다: 위험이 계속 핵심 수식이 아니라 설정과 권한 계층에 내려앉았다는 것.

2026-08-25 보강 — 메커니즘 규명 완료, 카드 종료

DAO 제안이 기술적으로 무엇인가. 정책 문서가 아니라 실행될 트랜잭션 그 자체입니다. Governor는 propose(address[] targets, uint256[] values, bytes[] calldatas, string description)을 노출하고, 실행은 targets[i].call{value: values[i]}(calldatas[i]) 루프입니다. 거버넌스 컨트랙트는 요청받은 임의의 호출을 대신 해주는 기계이고 투표는 그 방아쇠입니다. 그리고 description 필드는 온체인에서 강제되지 않습니다 — 제안문에 "Q3 전략 리밸런싱"이라 쓰고 calldata에 전혀 다른 것을 넣을 수 있고, 구속력을 갖는 것은 calldata뿐입니다.

그래서 공격자가 한 행위는 둘로 줄어듭니다: 트랜잭션을 작성한다(누구나 가능) + 투표에서 이긴다(토큰을 사면 됨). 권한 상승도, 재진입도, 오버플로우도 없습니다. DAO를 설계된 그대로 썼습니다.

calldata에 무엇이 들어갔나. 동원된 Yearn v3 함수는 전부 평범한 운영용입니다 — set_role · add_strategy · update_debt · shutdown_vault. 보고된 실행이 aEthWETH 44.37개를 소각하고 stataEthWETH 래퍼로 Aave에서 WETH를 인출한 것은 정확히 update_debt(aaveStrategy, 0)의 온체인 서명입니다 — 외부 포지션을 먼저 금고로 회수시키고, 그다음 방향을 돌린 것. 포스트모템이 없어 추론이지만 순서는: 공격자 컨트랙트를 전략으로 등록 → Aave 전략 목표 부채를 0으로 → 회수된 잔액을 공격자 전략으로 배분 → 인출. 정상 리밸런싱과 이 공격의 차이는 두 번째 인자의 20바이트 주소 하나입니다. 불변식 위반을 감시하는 모니터링은 울리지 않습니다 — 위반된 불변식이 없으니까요.

분모가 셋인데, 공격 비용은 셋째뿐입니다. 총 발행량의 51%를 사는 비용이 사람들이 인용하는 숫자이고, 유통 물량의 51%가 조금 더 가깝고, 실투표(turnout)의 51%가 실제로 지불된 값입니다. 락업된 팀·재단 물량은 투표하지 않고 유통 물량 보유자도 대개 투표하지 않으므로, 총 발행량의 한 자릿수 퍼센트를 가진 공격자가 행사된 표의 100%를 가질 수 있습니다 — 보고된 수치가 서술하는 상태가 정확히 그것입니다. 정족수를 총 발행량 기준으로 걸었다면 비용이 자릿수 단위로 올랐겠지만, 의미 있을 만큼 높은 정족수는 DAO를 마비시키므로 프로토콜들이 완화하고, 그 완화가 곧 할인입니다.

기존 방어가 왜 안 걸렸나. 7일 타임락은 통제가 아니라 지연입니다 — 보안 속성을 운영 속성으로 바꿔서, 누군가 제안 큐를 보고 있고 악성 페이로드를 알아보고 막을 권한이 있고 그 창 안에 살아 있어야 작동합니다. 그 루프를 아무도 안 돌렸습니다 — 사건을 처음 드러낸 것은 Term도 LP도 아닌 제3자의 모니터링 봇이었습니다. 무관심한 정족수가 쥔 LP 거부권도 행사 가능하지 않습니다 — 투표권이 100%에 도달하도록 방치한 집단이 곧 거부권을 행사하리라 기대되는 집단이니까요. 그리고 플래시론이 아니었다는 점이 상황을 더 나쁘게 만들었습니다: Beanstalk식 임차는 거대한 자금 서명을 남기지만, 그냥 살 수 있을 만큼 유통량이 얇으면 매집이 실행 직전까지 유기적 수요와 구별되지 않습니다.

해법을 '사람이 필요한가'로 정렬하면. 실행기를 사전 등록된 함수·주소 화이트리스트로 좁혔다면 add_strategy(공격자) 자체가 도달 불가였고, 이건 보는 사람이 없어도 작동합니다. 투표 가중치를 별도 토큰이 아니라 금고 지분에 묶으면 부등식이 자기강제됩니다 — 51%를 통제하려면 51%를 예치해야 하므로 훔친 것의 절반은 원래 자기 돈입니다. 토큰과 무관한 가디언 키는 타임락 중 취소가 가능합니다. 타임락만 늘리는 것은 아무것도 바꾸지 못합니다 — 그것이 의존하는 루프가 애초에 돌지 않았으니까요.

왜 반복되는가. Beanstalk 이후 업계는 투표권 스냅샷·투표 지연·타임락을 도입했고, 이것들은 플래시론 거버넌스 공격을 사실상 완전히 막습니다. 그리고 Term은 그냥 사서 들고 있음으로써 전부 통과했습니다. 벡터가 패치됐지 클래스가 패치되지 않은 것. 한편 그 구성 자체가 업계 기본값입니다 — role_manager를 멀티시그에 두면 중앙화 비판을 부르고, 거버넌스 권한을 좁히면 토큰 유틸리티가 줄어 가격이 내리고 그러면 남은 투표권을 사는 비용도 같이 내려갑니다. 보안과 탈중앙 서사가 정반대로 당기고, 그래서 이 비율을 계산할 사람이 어느 팀의 업무기술서에도 없습니다.

종료 상태. 메커니즘·세 분모·타임락과 거부권의 실패·해법의 우선순위까지 여기 기록됐습니다. 남은 것은 살아 있는 프로토콜에 대한 산수이고, 그건 범위 설정이 아니라 후속 작업입니다 — Snapshot·Tally에서 과거 실투표량을 긁고, DEX 깊이에 대해 결정적 가중치의 가격을 매기고, 거버넌스가 통제하는 TVL에 대보는 것. 그리고 이 불변식은 시간에 따라 변한다는 점을 함께 적어둡니다 — 출시 시점엔 만족했을 가능성이 크고 토큰이 하락하며 조용히 뒤집혔을 겁니다. 그래서 산출물은 감사가 아니라 모니터여야 합니다.

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑