# Stale approval survives reversal

Cancel, burn, detach, rotate or delete paths that drop state but leave approvals, roles or indexes behind

_Area: Smart contract access control and control flow. Seen in 52 disclosed reports; latest 2026-08-20. Updated 2026-10-23._

## How it works
A capability (approval, role, owner slot, index entry, lifecycle flag) is granted as a side effect of an action and is supposed to die when that action is reversed. The reversal path removes the visible object but not the capability. The broken assumption is "removing the object removes its authority." The invariant that must hold: the set of capability holders equals exactly the set justified by current state. Sub-mechanisms recur: (a) missing revocation — the cancel/burn/detach/rotate branch never touches the parallel approval or role map [E1](https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md)[E4](https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md)[E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md); (b) skipped clear-helper — cleanup exists but a guard like `if (old != new)` returns early when the value is already zero, so nothing is cleared [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md); (c) mismatched revoke key — the revoke passes a different parameter than the grant, so it targets a different storage slot [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md); (d) stale lifecycle flag — a privileged-action gate is only reset by a later cleanup step, so re-activating the object before cleanup leaves the flag set [E5](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md); (e) role anchored at init to one address and never rotated alongside the primary admin [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md).

## Where it shows up
- Cancel, refund, expiry, burn, unlink, detach, or delete branches that touch one mapping but not a parallel approval/role/index map [E1](https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md)[E4](https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md)[E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md)
- Clear-helpers with early-return guards on already-zero values; in E3 the guard is on the manager value, not the approval value [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md)
- Same privileged address seeded into two roles at init, rotated in only one [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md)
- Revoke paths that hardcode a default (empty rights/salt) while the grant used a caller-supplied value [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md)
- A gate flag reset only in a cleanup step gated on unrelated conditions, while the guarded object can be re-activated first [E5](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md)
- Grep for `_clearApproval`, `delete`, `unlink`, `detach`, `unregister`, `accept_admin`, `revoke` and check each has a matching revoke with identical parameters.

## How to review
For every grant, enumerate every path that reverses the triggering action and confirm a matching revocation on each branch, not just the happy path.
- After cancel/burn/detach/delete/rotation, no approval, role, pending request, or index entry attributable to the prior holder remains.
- Each parallel approval map is cleared on every branch [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md).
- Teardown reconciles off-chain/on-chain signer validity with the actual owner set [E1](https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md).
- Revoke passes the same key parameter as the grant [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md).
- Every gate flag is cleared on the path that re-activates the guarded object, not only in a later cleanup [E5](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md).

## Variants seen
- Detach leaves stale Safe owners holding multisig rights [E1](https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md)
- Burn keeps per-token approval; zero-address transfer revives token (also needs missing from==0 guard) [E2](https://bugbountylibrary.com/r/c4%3A2022-09-artgobblers%3AH-01.md)
- Transfer branch skips approval clear, prior approver reclaims key [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md)
- Bid cancel keeps spender approval, enabling theft of auto-approved listing [E4](https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md)
- Re-onboarded term keeps stale offboard flag, replaying forced offboarding [E5](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md)
- Admin rotation leaves stale emergency-pause authority [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md)
- Revoke with hardcoded rights never clears custom-rights delegation [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md)

## Why fixes fail
Patching one reversal path while leaving sibling paths (expiry, refund) unpatched leaves the same stale grant reachable [E4](https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md). A validator accepting "stored role OR current admin" silently preserves the old holder [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md). A clear-helper guarded on value change skips cleanup exactly when it matters [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md). A revoke that hardcodes defaults is a no-op for non-default grants [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md).

## Impact and severity
Led to multisig takeover [E1](https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md), free re-minting of burnt assets [E2](https://bugbountylibrary.com/r/c4%3A2022-09-artgobblers%3AH-01.md), reclaim of sold keys [E3](https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md), theft of listed NFTs [E4](https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md), forced offboarding bypassing governance [E5](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md), retained pause authority [E6](https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md), and unrevocable delegations [E7](https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md). Severity rises when the stale grant is directly spendable by an attacker-controlled address or survives ownership changes.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2024-11-hats-protocol%3AH-1.md
- E2: https://bugbountylibrary.com/r/c4%3A2022-09-artgobblers%3AH-01.md
- E3: https://bugbountylibrary.com/r/c4%3A2021-11-unlock%3AH-04.md
- E4: https://bugbountylibrary.com/r/c4%3A2024-10-coded-estate%3AH-05.md
- E5: https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-05.md
- E6: https://bugbountylibrary.com/r/c4%3A2026-04-k2%3AM-10.md
- E7: https://bugbountylibrary.com/r/c4%3A2024-06-gondi%3AM-01.md
- E8: https://bugbountylibrary.com/r/sherlock%3A2024-10-debita%3AM-22.md
