Action-Bundle Non-Commitment / Delayed-Execution Shell Ambiguity
- Name: action-bundle non-commitment / delayed-execution shell ambiguity
- Category: exploit pattern / governance execution failure / delayed privileged-execution under-specification / authorization-design failure
- Summary: Some governance and timelock systems delay
a proposal,an executor, orthis queued operation idas if that label were the real thing being trusted. The real security object, however, is one exact privileged effect set: ordered targets, values, calldata, execution primitive, dependency edges, and any code identity that must still hold at execution time. When the system never actually commits that full action bundle, attackers can smuggle dangerous authority through hidden branches, mutable executors, delegatecall-heavy proposal shells, or other execution-time drift. By the time execution arrives, the protocol can still honestly saythis is the approved proposal, even though review and execution no longer refer to one exact machine-checkable story. - What it is:
- A protocol uses governance, a timelock, or another delayed privileged-execution path to authorize something that will run later.
- Reviewers, voters, or defenders inspect a proposal shell, high-level description, or current code artifact rather than one exact frozen effect set.
- The delayed object still has room to mean more than one materially different privileged action by the time execution happens.
- The attacker exploits that ambiguity to smuggle in authority the review surface did not really bind.
- The profit comes from the gap between
what the system delayedandwhat later execution was actually allowed to do.
- Typical preconditions:
- The protocol queues or votes on a proposal address, executor address, or abstract proposal id instead of exact ordered calls.
- The execution path uses
delegatecall, generic dispatch, module install, or another primitive that gives the delayed object wide authority once it runs. - Review depends on prose, source snapshots, or benign-looking top-level behavior while hidden branches, mutable code identity, or delayed state-dependent behavior still matter.
- Defenders cannot machine-check one exact action bundle from the queued object alone.
- The system lacks execution-time validation that the privileged effects still match what governance or review actually approved.
- Common vulnerable surfaces:
- governance systems that approve proposal contracts and later
delegatecallinto them - timelocks that queue a mutable executor shell instead of exact
targets[],values[], andcalldatas[] - module / plugin governance where the queued object can still resolve to wider behavior at execution time than reviewers authenticated
- admin frameworks where proposal descriptions are stable but executable artifacts, code identity, or dependency edges remain mutable
- delayed execution flows where monitors can see
something is queuedbut cannot reconstruct the exact privileged effect set from onchain commitments
- governance systems that approve proposal contracts and later
- Exploit sequence:
- The attacker prepares a proposal, executor shell, or queued operation that looks routine enough to survive review or voting.
- Governance or another approval path authorizes that shell for later execution.
- Before or during execution, the attacker uses the remaining ambiguity: hidden branches, mutable code behind the approved shell, delegatecall semantics, post-approval redeployment, or another under-committed execution detail.
- The privileged executor still accepts the action because the delayed object matches at the wrong abstraction level (
same proposal,same address,same queued id). - The attacker turns that already-approved ambiguity into storage writes, upgrade control, forged balances, or direct asset movement.
- Variants:
- Mutable proposal shell — governance approves a proposal contract or executor shell, but later execution depends on code identity that was never pinned.
- Description-to-bytes drift — humans review a benign-looking explanation or source diff, while the actual queued bytes still encode broader behavior.
- Delegatecall authority smuggling — the delayed object looks like a helper but later executes inside privileged storage, so any hidden branch becomes root authority.
- Operation-id ambiguity — a queue id or proposal label stands in for multiple materially different possible effect sets.
- Hidden-later-stage branch — the reviewed path looks routine, but a delayed branch such as
selfdestruct, factory replay, module rebinding, or emergency helper changes what later execution really means.
Three delayed-execution promotions reviewers should not collapse
-
A proposal shell or queued object exists at all
- Governance has a proposal id, a queued address, an operation hash, or some other delayed object that the system recognizes as eligible for later execution.
- That is only the weakest fact:
something delayable was registered.
-
That delayed object counts as one exact privileged effect set
- The harder question is whether the queued thing really commits ordered targets, calldata, value, execution primitive, dependency edges, and any code identity whose drift would change meaning.
- Most failures in this family happen here: the protocol promoted
this proposal existsorthis shell was approvedintoreviewers and executors are now talking about one exact machine-checkable future action bundle.
-
That committed effect set deserves this much live authority when execution opens
- Even a well-specified action bundle should not automatically inherit unlimited treasury, upgrade, module-install, or delegatecall authority without further blast-radius controls.
- This is where privilege tiering, staged execution, veto paths, and bounded executors start to matter.
Healthy systems keep those three promotions separate instead of flattening them into one vague comfort claim like the proposal passed or the timelock queued it.
- Detection heuristics:
- Ask
what exact machine-checkable effect set did the delay freeze?If the answer is mostly a label, address, or prose description, the system is under-committed. - Compare review-time artifacts versus execution-time artifacts: code hash, implementation pointer, exact calldata list, call primitive, and dependency constraints.
- Treat delayed
delegatecallinto externally supplied proposal contracts as high sensitivity even when the proposal looks conventional. - Look for proposal systems where defenders can inspect intent socially but cannot reconstruct one precise execution bundle from the timelock or governor state itself.
- Search for destructive or metamorphic branches (
selfdestruct, factories, module installs, upgrade hops), but remember the deeper issue is the protocol authorizing an underspecified shell at all.
- Ask
- Defenses:
- Queue exact ordered
targets,values, andcalldataswhenever possible instead of approving open-ended executors. - Bind delayed execution to code identity whenever executable contracts or modules are part of the plan.
- Revalidate runtime or implementation hash, call primitive, and dependency edges at execution time.
- Keep monitors, veto paths, and reviewers looking at the same machine-readable object the executor will later honor.
- Minimize or eliminate
delegatecallinto proposal-controlled code; prefer bounded action lists or narrowly scoped verified modules. - Treat proposal factories, module registries, and proxy admins that can change execution meaning after approval as part of the same governance threat model.
- Queue exact ordered
What sound design looks like
- Healthy systems keep three facts separate on purpose:
a proposal shell or queued object exists,that delayed object really commits one exact privileged effect set, andthat committed effect set deserves this much live authority when execution opens.The proposal passedis only the beginning of review, not the end of it. - Healthy systems delay one exact privileged-execution object, not a story about one.
- The object should preserve ordered targets, exact calldata, value transfer, execution primitive, predecessor / timing rules, and any code identity whose drift would change privileged meaning.
- A reviewer should be able to say
this queued thing can only do these exact effects laterwithout relying on mutable offchain interpretation. - If the protocol truly needs executable proposal contracts, that mutability has to be pinned or re-proved at execution time; otherwise the delay only protects a shell.
- Even fully specified action bundles should not automatically inherit flat root authority. Healthy designs still narrow execution power with bounded executors, staged upgrades, rate limits, veto windows, or post-execution sanity checks.
Failure-prevention checklist
- Write down, per delayed governance or timelock path, which promotion is happening and where:
a proposal shell exists,that shell is bound to one exact privileged effect set, andthat bound effect set may now exercise treasury / upgrade / module-install / delegatecall authority. Do not let one queue or vote success silently stand in for all three. - Queue and hash one exact privileged action bundle: ordered
targets,values,calldatas, execution primitive, predecessor dependencies, salt / nonce, and timing window. If any of those can still drift, the protocol has not actually delayed the real security object. - Pin code identity whenever execution depends on proposal contracts, modules, libraries, or helper executors. Runtime hash, implementation hash, immutability guarantee, or deployment provenance should be part of authorization, not offchain reviewer folklore.
- Keep
delegatecallout of governance-controlled proposal shells unless the callable surface is extremely narrow and machine-verifiable. A proposal contract running in governor or timelock storage is effectively root code. - Make the human review artifact derive from the same bytes the executor will honor later. UI summaries, forum posts, and audit notes should be secondary views of the committed bundle, not substitutes for it.
- Revalidate execution-time assumptions before privileged effects land: expected code hash, expected implementation pointer, expected target set, and expected call primitive should all still match the approved object.
- Treat proposal factories, module registries, plugin installers, and post-approval deployment steps as part of the same threat model. If governance approves a shell that later resolves through mutable infrastructure, the commitment is incomplete.
- Require fork or simulation traces against the final queued object, not only against source code or a pre-queue draft. Review should answer
what exact storage writes can this queued thing produce? - Give defenders a veto / pause surface that can intervene when a queued object is discovered to authorize more than the review surface made obvious.
- Emit machine-readable events or metadata that let monitors reconstruct the exact execution bundle without trusting prose, source links, or offchain social context.
- Keep the blast radius bounded anyway: privilege tiering, rate limits, staged upgrades, and post-execution sanity checks should assume one ambiguous governance object will eventually slip through.
Audit prompts
- What exact object is being voted on or queued: a proposal id, an address, a code hash, or one ordered call bundle with committed bytes?
- Can execution meaning change if the approved address later points to different runtime code, a new implementation, a new module binding, or a factory-produced helper?
- Does any path let governance-approved code run through
delegatecall, plugin install, module enablement, or another primitive that expands authority beyond the reviewed top-level calls? - Can reviewers reconstruct the full privileged effect set from onchain governor / timelock state alone, or do they need forum context, source assumptions, or trust in unchanged deployments?
- Are there hidden later-stage branches whose meaning depends on state that can change after voting: implementation slots, registry lookups, module registries, factory outputs, self-destruct / redeploy paths, or emergency flags?
- Do proposal descriptions, rendered UIs, or explorer views omit targets, calldata, nested calls, or execution primitives that materially affect authority?
- At execution time, what proves that the queued object is still the same object governance reviewed? Where is code identity or effect identity checked, if anywhere?
- Could a benign-looking helper call install a new authority surface, widen approvals, hand over upgrade rights, or rewrite storage far outside the apparent scope of the proposal?
- What operator or monitor path can stop execution after a hidden branch is discovered but before treasury or upgrade authority is lost?
- If a proposal were malicious but fully specified, would defenders notice earlier than they notice an underspecified shell? If yes, the review surface and authorization surface are misaligned.
Lessons that should already be standard
A proposal exists,that proposal commits one exact privileged effect set, andthat effect set deserves live root-ish authorityare three different claims.- A timelock on a shell is not a timelock on effects. Delaying
this addressorthis proposal idis meaningless if the privileged meaning still lives somewhere mutable. Delegatecallturns review mistakes into root-authority mistakes. Governance proposal code executed in privileged storage should be treated like an upgrade, not like a helper script.- Human-readable proposal descriptions are commentary, not commitments. The executor only cares about bytes, code identity, and execution primitives.
- Code identity is part of authorization whenever later execution depends on executable artifacts.
Same addressis not the same thing assame privileged behavior. - Monitors need one machine-checkable object to defend. If defenders must piece together intent from forum posts, verified source, and current code at multiple addresses, the system has already made review too social.
- Delayed execution should reduce uncertainty over time, not preserve it. The further a proposal gets toward execution, the narrower its possible privileged meaning should become.
- Governance safety is not just about who can vote. It is also about whether the thing being approved is precise enough that an honest vote corresponds to one exact future effect set.
- Post-approval mutability is itself privileged power. Factories, registries, module lookups, and implementation pointers can be as security-critical as the top-level timelock queue.
Where this pattern stops
- If the decisive bug is that an approved address later resolved to different code, and that code-identity drift is the whole exploit, mutable-code execution is usually the sharper pattern.
- If the attacker simply rented, borrowed, or accumulated enough legitimate voting power to pass a malicious but fully specified action bundle, governance capture or borrowable governance weight is the cleaner frame.
- If the attacker already controls the formal upgrader or admin key and uses that authority directly, privileged-upgrade abuse is the narrower lens.
- This note begins earlier, at the moment the protocol mistakes an under-specified delayed shell for a fully authenticated privileged action bundle.
High-signal incidents
- Tornado Cash governance proposal-smuggling takeover (2023-05-20) — the clearest case where governance delayed a proposal shell instead of one exact effect set, then executed swapped-in malicious logic through
delegatecallafter approval. - mutable-implementation approval drain (2021-02-27) is the adjacent non-governance cousin where approval focused on the shell (
this approved target) while later execution meaning drifted elsewhere.
Internal linkages
- Broader governance framing: governance-capture.
- Narrower mutable-code branch: approval-by-address-mutable-code-execution.
- Adjacent root-authority branch: privileged-upgrade-abuse.
- Positive-design companions: execution-envelope identity pinning, timelock action-bundle commitment.
- Best current incident exemplar: tornado-cash-governance-proposal-smuggling-takeover-2023-05-20.
Control surface
-
The leverage sits in what the protocol treats as the delayed security object: raw proposal address, operation id, executor shell, exact ordered calldata list, runtime or implementation hash, and the
call/delegatecallprimitive that turns later execution into root authority. -
The recurring mistake is to collapse
a proposal shell exists,that shell really commits one exact future effect set, andthat accepted effect set may now exercise live privileged authorityinto one vague approval decision. -
If the queue only freezes a shell while the real privileged meaning is still somewhere else, the attack surface is already live before voting even ends.
-
The core question is never just
was there a timelock?It iswhat exact effect set did the timelock actually make immutable? -
Sources:
- timelock-action-bundle-commitment
- execution-envelope-identity-pinning
- approval-by-address-mutable-code-execution
- tornado-cash-governance-proposal-smuggling-takeover-2023-05-20
- https://docs.openzeppelin.com/contracts/5.x/api/governance
- https://blog.openzeppelin.com/protect-your-users-with-smart-contract-timelocks
-
Last reviewed: 2026-07-31 UTC