# Bridge watcher can forge arbitrary message and drain bridge

Taiko bridge_watchdog could un-suspend an unproven message, marking it proven via nonzero receivedAt, then process forged messages to drain bridge ETH/tokens.

- Payout: Contest reward
- Severity: medium
- Program: Taiko
- Credited: monrel
- Disclosed: 2024-04-26
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2024-03-taiko-findings/issues/278
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-taiko%3AM-05
- Tags: domain:web3 impact:funds-loss impact:privesc kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic vuln:signature-replay

**Target:** Bridge.sol suspendMessages/processMessage proof receipt logic
**Root cause:** suspendMessages sets proofReceipt[msgHash].receivedAt to block.timestamp on un-suspend, and processMessage treats any nonzero receivedAt as proven, so an unproven hash becomes 'proven'.
**Steps:**
1. As bridge_watchdog, call `suspendMessages([msgHash], true)` for an arbitrary unproven message hash.
2. Call `suspendMessages([msgHash], false)`; receivedAt becomes block.timestamp (nonzero).
3. Call `processMessage(message, proof)` with any proof; isMessageProven = receivedAt != 0 passes.
4. Message executes, transferring value to attacker-chosen recipient.
**How found:** Code reading of suspendMessages and processMessage: noticed un-suspend writes block.timestamp into receivedAt, and that processMessage only checks receivedAt != 0 for proven status.
**Exploitation:** Malicious/compromised bridge_watchdog forges a message with value 10 ether to an attacker address, flips its proof receipt via suspend then un-suspend, and calls processMessage with dummy proof `0x00` to execute the transfer, draining bridge ETH/tokens.
**Escalation:** Privilege escalation: role intended only to ban/suspend (DoS) gains ability to forge proofs and drain all bridge funds.
**Insight:** When a pause/ban admin function writes state used as a proof/validity flag, check whether the 'resume' path writes a truthy value instead of clearing it; any nonzero sentinel can be mistaken for a valid proof.
