Security Vulnerability Disclosure: Fixed L1 → L2 Migrator Address Validation

Hi all. A protocol bug in the L1 → L2 migrator, responsibly disclosed through the Livepeer Immunefi Bug Bounty program, has now been patched. Sidestream led the analysis and patch, then coordinated final review and execution with the security committee. The bug, if exploited, would have allowed any L1 address to force a delegate change on an L2 delegator they do not own. No user funds were lost, the issue was not exploited on the network to our knowledge, and the system is now fully patched. A bounty has been paid to the reporter, and we thank them for their responsible disclosure.

The issue

The L1 → L2 migrator entry points (migrateUnbondingLocks and migrateDelegator on L1Migrator) authenticated the L1 owner of a migration request, but did not verify that the specified L2 destination address belonged to the same party. Because the L2 Migrator is a trusted caller of the L2 BondingManager, the cross-chain payload could force a delegate change on an arbitrary L2 delegator chosen by the caller.

In practice, this had two effects:

  • Griefing. A caller could set a victim’s L2 delegate to address(0), making their stake inactive (no voting power or rewards until they re-delegate). Cumulatively, this reduces the next round’s total active stake, which in turn deflates the governance quorum threshold. If the transcoder hadn’t yet called reward() that round, the victim also missed that round’s rewards.
  • Voting-power concentration. A self-delegated L1 caller could redirect victims’ delegated stake to themselves on L2, accumulating governance voting power. In theory, this could have led to passing a treasury-affecting proposal.

The second path was bounded in practice: it required specific attacker preconditions (self-delegated as a registered transcoder on both L1 and L2), would have been visible on-chain throughout, and would have taken approximately 8 days to play out, giving the protocol and community time to detect and respond. No exploitation occurred.

The fix

L1Migrator was patched to:

  • Enforce that the L1 source and L2 destination addresses are identical in requireValidMigration via require(_l1Addr == _l2Addr, "L2_ADDR_MISMATCH").
  • Reject empty unbonding-lock-ID arrays (require(unbondingLockIdsLen > 0, "EMPTY_LOCK_IDS")) and zero-amount lock entries (require(amount > 0, "ZERO_LOCK_AMOUNT")) in getMigrateUnbondingLocksParams.

Together, these checks eliminate both disclosed paths.

This was addressed in two steps:

  • Interim mitigation. L1Migrator was paused via the L1 governance multisig, neutralizing the attack vector while the fix was developed.
  • Full fix. L1Migrator is not a proxy and cannot be upgraded in place, so the patched logic shipped as a new contract at 0x2a69191B43c9DB47C927bD7287F9C93838d07759. The L2Migrator was then repointed to it to it by the L2 governance multisig, so cross-chain messages are accepted only from the new contract.

The original L1Migrator remains paused and is no longer used.

Behavioural change and multisig compensation

Why the L1 ≠ L2 path existed originally. The migrator lets you specify a different L2 destination than the L1 source because not every delegator is an EOA. Some are contract accounts (multisig wallets) that can’t guarantee the same address on Arbitrum, so they need a different L2 destination. The fix removes that flexibility, which was exactly what made the bug exploitable.

Affected accounts. 6 multisig wallets remain on L1 with approximately 459 LPT bonded (around 1,050 LPT of compounded value on L2 as of the snapshot at L1 block 24,935,949, ≈ late April 2026). The scan used to identify them is published here and is reproducible against any L1 archive node.

Compensation. Reopening the differing-address path would have meant adding new on-chain logic. Given the small, known exposure, direct compensation is the lower-risk tradeoff. So instead, the Livepeer Foundation will send unbonded LPT directly to each multisig’s L1 address. Each gets its compounded snapshot value plus a generous two years of staking rewards at a flat 60% annual rate, roughly 2,700 LPT in total across the six. These transfers go straight to the identified L1 addresses, so no action is required from the holders.

Everyone else. All other migration paths remain open. EOAs can continue to use L1Migrator.migrateUnbondingLocks and migrateDelegator (with the new same-address requirement), or L2Migrator.claimStake (the Merkle-snapshot-based path, which has been continuously available throughout and is unaffected by the L1 system pause).

No LIP required

The security committee deployed this patch under its existing upgrade authority to protect user funds. LIP-25 carves critical bug fixes and emergency pauses out of the LIP process, so no LIP is required. As with prior security patches, the public record is a forum disclosure like this one.

Thanks and pointers

Thanks again to the reporter for the responsible disclosure, and to Sidestream, our protocol security partner, for their thorough and timely handling of the triage, analysis, mitigation design, and patch development through to deployment. The deployed contract and patch are linked below for anyone who wants to verify directly.

Sidestream can share further info on community request.

For questions, email security@livepeer.foundation or reply in this thread.

3 Likes