# Owners can be swapped even though they still wear their signer hats

HatsSignerGateBase's checkAfterExecution omits owner-change validation, letting colluding signers delegate-call to swap Safe owners while replaced signers still hold signer hats.

- Payout: Contest reward
- Severity: medium
- Program: Hats
- Credited: Dug
- Disclosed: 2023-02-15
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-02-hats-judging/issues/118
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-hats%3AM-2
- Tags: domain:web3 impact:privesc kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:governance

**Target:** HatsSignerGateBase / Safe multisig owner management
**Root cause:** checkAfterExecution validates modules but not the Safe owner set, so a delegatecall can invoke swapOwner directly, bypassing _swapSigner's requirement that the removed owner no longer wears a signer hat.
**Steps:**
1. Deploy malicious contract exposing a function that calls Safe's `swapOwner(prevOwner, oldOwner, newOwner)`
2. Craft a multisig transaction that delegate-calls the malicious contract from the Safe
3. Colluding signers approve and execute; checkAfterExecution passes because owners are not compared pre/post
4. Opposing signer is replaced while still wearing their signer hat
**How found:** Reviewed checkAfterExecution and noticed it compares modules pre/post-flight but omits the owner set, then observed _swapSigner's hat check could be sidestepped via delegatecall to swapOwner.
**Exploitation:** A colluding quorum of signers submits a delegatecall to a malicious contract that invokes swapOwner, replacing non-cooperating owners with cooperating ones; since checkAfterExecution only diffs modules, the swap succeeds and the colluders gain durable control of the Safe.
**Insight:** When a guard validates state deltas after execution, enumerate every privileged setter on the guarded contract (owners, thresholds, modules, guards) and confirm each is diffed; any unchecked setter reachable via delegatecall is a bypass.
