# anyone with a Pearlmit approval to transfer TapToken can have their funds stolen

TapToken.transferFrom falls back to Pearlmit allowance check against msg.sender (the token itself), letting anyone spend approvals granted to the TapToken contract and steal TAP.

- Payout: Contest reward
- Severity: medium
- Program: Tapioca DAO
- Credited: immeas
- Disclosed: 2024-05-23
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-02-tapioca-findings/issues/172
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-tapioca%3AM-04
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** TapToken / BaseTapiocaOmnichainEngine transferFrom
**Root cause:** When the direct allowance is insufficient, transferFrom calls pearlmit.transferFromERC20, which checks allowance against msg.sender — the TapToken contract — rather than the actual caller, so approvals granted to the token contract are spendable by anyone.
**Steps:**
1. userA calls `pearlmit.approve(address(aTapOFT), 0, address(aTapOFT), 1e18, type(uint48).max)` and `aTapOFT.approve(address(pearlmit), 1e18)`
2. userB calls `aTapOFT.transferFrom(userA, userB, 1e18)`
3. allowance(from, msg.sender=userB) is 0, so fallback `pearlmit.transferFromERC20(from, to, address(this), value)` executes
4. Pearlmit sees msg.sender = TapToken, which userA approved, and moves funds to userB
**How found:** Code reading of transferFrom's allowance fallback: noticed Pearlmit checks allowance against msg.sender, which inside the token contract is the token itself, not the external caller.
**Exploitation:** Attacker observes a victim's Pearlmit approval to the TapToken contract (plausible since TapToken handles tokens in composed messages), then calls transferFrom(victim, attacker, amount) directly; the fallback path spends the victim's Pearlmit allowance to the token contract.
**Insight:** When a token delegates allowance checks to an external permit/approval contract, verify which address becomes msg.sender in that call — a fallback that passes address(this) turns any approval to the token contract into a public spend authorization.
