# middle level admins can steal child trees because function unlinkTopHatFromTree() is callable by them

Mid-level admin in Hats protocol relinks a child tree under a self-created tophat then unlinks it, seizing sole admin control of the subtree.

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

**Target:** Hats.sol tree admin functions (relinkTopHatWithinTree, unlinkTopHatFromTree)
**Root cause:** relinkTopHatWithinTree only checks old and new admin share the same tippy hat, and unlinkTopHatFromTree is callable by any admin in the tree, so a mid-level admin can relink a subtree under a self-created tophat then unlink it from the parent.
**Steps:**
1. Tree: Root0(admin0)->H1(Admin1)->H2(Admin2)->Root3(Admin3)->H4(Admin4)
2. Admin2 creates new TopHat Root1 and links it: `H2 -> Root1`
3. Admin2 calls `relinkTopHatWithinTree(Root3, Root1)` since both Root3 and Root1 are under H2
4. Tree becomes Root0->H1->H2->Root1->Root3->H4
5. Admin2 calls `unlinkTopHatFromTree(Root3)` detaching Root1 subtree from Root0 tree
6. Admin2, as creator/admin of Root1, is now sole admin of Root3->H4
**How found:** Code reading of relink/unlink permission checks: noticed relink only verifies old and new admin share the same tippy hat, and unlink is callable by any admin, so combining the two bypasses the intended same-tree restriction.
**Exploitation:** A trusted mid-level admin performs one relink to move a child tree under a tophat they control, then unlinks it, cutting off all higher-level admins and becoming sole admin of the stolen subtree.
**Insight:** When a protocol restricts an action (relinking across trees) by checking shared ancestry, look for a second primitive (unlink) callable at a lower privilege that can be composed to achieve the forbidden state; test whether relink+unlink equals a cross-tree move.
