# Signers can bypass checks and change threshold within a transaction

HatsSignerGate post-flight threshold check compares against recomputed value, so signers can remove owners in the same tx to lower the safe threshold.

- Payout: Contest reward
- Severity: high
- Program: Hats
- Credited: cducrest-brainbot
- Disclosed: 2023-02-15
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-02-hats-judging/issues/52
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-hats%3AH-1
- Tags: domain:web3 impact:privesc kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic

**Target:** HatsSignerGateBase.sol Zodiac guard on Safe
**Root cause:** checkAfterExecution compares safe.getThreshold() to _getCorrectThreshold() recomputed after the tx, not to the pre-tx threshold, so changing owner count also changes the expected value.
**Steps:**
1. Deploy safe with 8 owners all valid signers, targetThreshold=10, minThreshold=2; threshold=8.
2. Signer submits tx calling removeOwner() (or multicall of removeOwner) to drop an owner.
3. Safe adjusts threshold to 7 during execution.
4. checkAfterExecution runs: _getCorrectThreshold() now returns 7 (count of valid signers), matches safe.getThreshold(), check passes.
**How found:** Code review of checkAfterExecution invariant: noticed _getCorrectThreshold() is recomputed post-tx from current owners, so it is not a fixed reference point; hypothesized that mutating owners in same tx keeps check satisfied.
**Exploitation:** Malicious signer set uses Safe multicall to repeatedly removeOwner, lowering threshold and consolidating control over future transactions, breaking the documented invariant that signers cannot change threshold.
**Escalation:** Multicall removeOwner multiple times to reduce threshold more dramatically; combined with tampering hat status/eligibility if safe controls signersHat modules.
**Insight:** When a post-execution invariant check recomputes its expected value from live state, look for ways to mutate that state within the same transaction (e.g. owner/module changes via multicall) so the check compares against a shifted baseline; snapshot the expected value pre-tx instead.
