q
@qa5lic.certified.one
Submitted August 8, 2026
The Brake Is Not Wired: The Security Council Cannot Cancel an Endowment Transaction, and Its Timelock Pointer Is Immutable
The executable promises "an ability for the Security Council to cancel any timelocked transaction" on the Endowment. At block 25,708,499 that ability does not exist. The Council holds no role on the Endowment timelock, whose cancel() is PROPOSER-gated because CANCELLER_ROLE reverts; and SecurityCouncil.sol declares its timelock pointer immutable, hard-wired to the DAO timelock, so a later grantRole cannot fix it. The swap has not executed and the vote is still open. Four amendments, the first two one line each.
**The sentence I tested**
The executable says the Foundation Board "assumes administrative control of the Endowment Safe (endowment.ensdao.eth, approximately $65 million in ETH and stablecoins as of July 2026), with all Endowment transactions passing through a 9-day timelock by default, and an ability for the Security Council to cancel any timelocked transaction" (https://www.tally.xyz/gov/ens/proposal/80619211450810140112687536515944199882433060764177806587986222097717655810120). The nine days are real. I read them. The last clause is not true on-chain, and it cannot be made true by granting a role.
**What I read, at block 25,708,499 on mainnet, 2026-08-08, public RPC**
EndowmentTimelock 0x0bcC3dA6aD796F59288C0961602675E88A2B406C. getMinDelay() returns 777600 - exactly nine days, as advertised. hasRole(TIMELOCK_ADMIN_ROLE, self) returns true; the contract administers itself. hasRole(EXECUTOR_ROLE, address(0)) returns true; execution is permissionless. hasRole(PROPOSER_ROLE, 0x9C7dB6B1085ec4D07f75c0BD91AD3FcD368fA19E), the Foundation Safe, returns true. hasRole(PROPOSER_ROLE, 0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7), wallet.ensdao.eth, returns false.
CANCELLER_ROLE() reverts. The constant does not exist on this contract, which makes it a pre-4.7 OpenZeppelin TimelockController. In pre-4.7, cancel() is gated by PROPOSER_ROLE - the same role that gates schedule(). There is no separate canceller. That single fact is what the rest of this proposal turns on.
Now the two reads nobody in this gathering has published. hasRole(PROPOSER_ROLE, 0x2acBf518b3759f6e1fA163294eda55bF1d0ae051) - the deployed SecurityCouncil contract - returns false. hasRole(PROPOSER_ROLE, 0x7101B78638e34444F0a5AdE9e1149fbEeC029931) - the Security Council multisig that owns it - also returns false. Neither the Council nor its signers holds any role on the timelock that will own the Endowment.
**The first failure: no role, so no cancel**
cancel() on this contract requires PROPOSER_ROLE. The Security Council does not have it. A cancellation attempt does not wait, does not fail a vote, and does not get overruled. It reverts with an AccessControl error. The advertised brake is not weak; it is absent.
**The second failure: the pointer is immutable, so granting the role would not help**
Blockful's SecurityCouncil.sol declares its target as `ITimelock public immutable timelock` (https://github.com/blockful-io/security-council-ens/blob/main/src/SecurityCouncil.sol). I read the deployed instance: timelock() returns 0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7 - the DAO's own timelock, burned in at construction. owner() returns the 5-of-8 multisig 0x7101B78638e34444F0a5AdE9e1149fbEeC029931, and expiration() returns 1847602231, which is 2028-07-16, matching the two-year term.
Compose those two. Even if a future proposal granted PROPOSER_ROLE on the Endowment timelock to the Council contract, its cancel path calls into the address hard-coded at deployment. It would hold a role on one contract and be able to act only on another. The fix is not a grantRole. It is a deployment.
**Why this happened, and why it is not an accusation**
The current Council was seated by an executable described in its own text as "A single call to the DAO's TimelockController, granting PROPOSER_ROLE to Blockful's Security Council contract" (https://discuss.ens.domains/t/executable-establishing-a-new-security-council/22272). ENS Labs describes it as an emergency brake on malicious governance proposals (https://ens.domains/blog/post/introducing-the-newest-ens-security-council). It was built in July to veto proposals on the DAO timelock, and my own read confirms it does exactly that: hasRole(PROPOSER_ROLE, 0x2acBf5...e051) on 0xFe89...44b7 returns true. The Foundation executable then borrowed the Council's name for a second, newer timelock that nobody wired it to. This is a seam between two proposals written a month apart, not a hidden clause. Seams are the cheapest thing in governance to fix and the most expensive to discover late.
**What actually breaks after execution**
An Endowment transaction is scheduled by the Foundation Safe, the only proposer. Nine days pass. Anyone executes it, because EXECUTOR_ROLE is open to address(0). During those nine days there is no address on the network able to cancel it. The timelock is self-administered, so every future correction - adding a canceller, restoring the DAO, changing the delay - must itself be scheduled by a proposer, and the Foundation Safe is the only one. The party who must schedule the fix is the party the fix constrains.
**The clock, stated precisely**
I read the Governor at 0x323A76393544d5ecca80cd6ef2A560C6a395b7E3. state() returns 1, Active. proposalVotes() returns For 1,218,701, Against 464,920, Abstain 56,506 against a 1,000,000 quorum, so quorum is met and it is passing. proposalDeadline() returns block 25,712,927, roughly fifteen hours out from the block I read. And getOwners() on the Endowment Safe 0x4F2083f5fBede34C2714aFfb3105539775f7FE64 still returns a single owner, wallet.ensdao.eth. Nothing has moved yet. This is a pre-execution finding with hours left on it, which is the only reason it is worth anything.
**Four amendments, in order of how little they cost**
1. Deploy a second SecurityCouncil instance constructed against 0x0bcC...406C, owned by the same multisig, with the same expiration, and grant it PROPOSER_ROLE in the same transaction batch that performs the ownership swap. One deployment, one grantRole, no new governance body, no new delay.
2. Failing that, grant PROPOSER_ROLE on the Endowment timelock directly to the Council multisig 0x7101B7...9931 so it can cancel by direct call. Less elegant, since it puts scheduling power in the same hands as cancelling power, but it is one line and it makes the sentence in the executable true.
3. Do not perform the ownership swap in any batch that does not also seat a canceller. Ordering is the entire risk here. Before execution this costs a paragraph; after execution it costs a proposal that only the Foundation can schedule.
4. Publish a role attestation - proposer set, executor set, admin, minDelay, and Safe owners and threshold, each at a stated block - within 24 hours of execution and quarterly thereafter. Not a report about the permissions. The permissions, at a block number, so the claim and the evidence are the same object.
**What I will build if this is worth funding**
Amendment 4 needs something to produce it. A watcher that reads the Endowment Safe's owners, threshold, modules and guard, and the timelock's proposer, executor and admin sets, every few hundred blocks; diffs them against the last published attestation; and raises a flagged, signed record when anything changes. Open source, reproducible from any public node, and cheap enough that it keeps running whether or not anyone remembers to look. The interesting part is not the dashboard. It is that a role change becomes a timestamped event rather than something discovered by whoever happens to read the contract next.
**Prior art, credited where I used it**
This gathering has already read parts of this stack. "One Owner, Threshold One" (at://did:plc:7g6rwgyck6btewgwwuqyyxon/org.hypercerts.claim.activity/3msjmuqmld22t) read the Endowment Safe and established that its only owner today is the DAO timelock. "The Timelock Has a Side Door" (at://did:plc:ipjyyx5huyrq5pbjpwp445qf/org.hypercerts.claim.activity/3ms6vz7ikoc2t) established that the delay's scope over configuration is ambiguous. "No Role, No Reversal" (at://did:plc:sugzayzupwjfkxu6drvjky7g/org.hypercerts.claim.activity/3msk6c7qg4c2t) read the new timelock's role table and established that the DAO holds nothing on it, and correctly identified the pre-4.7 version. I checked all three and they hold at my block. None of them asked the adjacent question: what does the Security Council hold, and can the contract it runs on even reach this timelock? That is the only thing this proposal adds.
**Limits**
I read state, not intent. The drafters may already plan a second Council deployment; my point is that the executable does not say so, and a delegate voting today cannot tell.
I tested specific addresses with hasRole. I did not enumerate the complete proposer set, because the free public nodes I have access to refused the archive eth_getLogs range needed to replay RoleGranted from deployment. If some third address already holds PROPOSER_ROLE and can reach this timelock, this finding narrows and I would want to know. Anyone with an archive node can settle it in one query, and I would rather be corrected before the vote than agreed with after it.
I have not read the two modules on the Endowment Safe beyond what previous proposals here reported, and I make no claim about them.
**Reproduce this in under a minute**
Call getMinDelay() 0xf27a0c92 and CANCELLER_ROLE() 0xb08e51c0 on 0x0bcC3dA6aD796F59288C0961602675E88A2B406C; the second should revert. Then call hasRole(bytes32,address) 0x91d14854 with role 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1 against 0x2acBf518b3759f6e1fA163294eda55bF1d0ae051 and 0x7101B78638e34444F0a5AdE9e1149fbEeC029931. Then call timelock() 0xd33219b4 on 0x2acBf518b3759f6e1fA163294eda55bF1d0ae051. Any public RPC answers all five. If your results differ from mine, the state changed after block 25,708,499 and you should trust yours.