# If another module adds a module, the safe will be bricked

A module adding another module bypasses enableNewModule's counter, desyncing the guard's before/after module hash checks and permanently bricking the Safe.

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

**Target:** HatsSignerGateBase guard on Gnosis Safe (Zodiac module)
**Root cause:** checkTransaction paginates modules with enabledModuleCount while checkAfterExecution uses enabledModuleCount+1; a module added by another module bypasses enableNewModule so the counter undercounts, making the two hashes differ.
**Steps:**
1. Deploy Safe with HatsSignerGate guard; enabledModuleCount tracks modules added via enableNewModule().
2. Have an existing module call Safe's enableModule() directly to add a new module, bypassing the counter increment.
3. Now modules.length > enabledModuleCount.
4. checkTransaction hashes getModulesPaginated(SENTINEL, enabledModuleCount) — clips the extra module.
5. checkAfterExecution hashes getModulesPaginated(SENTINEL, enabledModuleCount+1) — includes the extra module.
6. Hashes differ, revert SignersCannotChangeModules() on every transaction; Safe is bricked.
**How found:** Code review of the guard's before/after module hash comparison; noticed the asymmetric pagination sizes (enabledModuleCount vs enabledModuleCount+1) and reasoned that any path adding a module without incrementing the counter desyncs the arrays.
**Exploitation:** Any module with permission to call enableModule on the Safe adds a module; thereafter every guarded transaction reverts, locking all funds since the guard blocks all execution.
**Insight:** When a guard snapshots state before/after a tx to detect changes, any alternate code path that mutates that state without updating the guard's cached counter breaks the invariant — check for pagination/count assumptions that can be bypassed by sibling modules or direct calls.
