On June 17, 2026 at 18:34:47 UTC, a legacy Aztec Private Rollup Bridge / RollupProcessor contract at 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba was drained in transaction 0xab306cd2184d23b6ba3e151b10b3b9a0b81f211cc16f4f3b0c79f0b17a59c2b5. A fresh EOA, 0x6952d9246e9afe8b887b2877225163436f78e97f, called escapeHatch(bytes,bytes,bytes), submitted a proof whose only public transaction requested a 1158 ETH payout to the same EOA, and received that full amount from the bridge. The contract balance dropped from 1158.759804206229645062 ETH to 0.759804206229645062 ETH in the same block. Aztec later attributed exploitation of the old immutable system to a claim-proof bug that allowed a malicious sequencer or escape-hatch caller to substitute arbitrary final outputs for a victim’s note.

Root Cause

Vulnerable Contract

  • Legacy immutable Aztec RollupProcessor / Private Rollup Bridge: 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba.
  • Proof verifier reached by the contract: 0x48cb7ba00d087541dc8e2b3738f80fdd1fee8ce8 (TurboVerifier).

Vulnerable Function

The externally called function was escapeHatch(bytes,bytes,bytes) selector 0xd1c65264. Once the escape-hatch window was open, the function forwarded attacker-supplied proof data into processRollupProof(...), which then derived numTxs = 1 from the proof header and executed processDepositsAndWithdrawals(...) on the public record embedded in that proof.

Vulnerable Code

function escapeHatch(
    bytes calldata proofData,
    bytes calldata signatures,
    bytes calldata viewingKeys
) external override whenNotPaused {
    (bool isOpen, ) = getEscapeHatchStatus();
    require(isOpen, "Rollup Processor: ESCAPE_BLOCK_RANGE_INCORRECT");

    processRollupProof(proofData, signatures, viewingKeys);
}

function processRollupProof(
    bytes memory proofData,
    bytes memory signatures,
    bytes calldata /*viewingKeys*/
) internal {
    uint256 numTxs = verifyProofAndUpdateState(proofData);
    processDepositsAndWithdrawals(proofData, numTxs, signatures); // <-- VULNERABILITY
}

function processDepositsAndWithdrawals(
    bytes memory proofData,
    uint256 numTxs,
    bytes memory signatures
) internal {
    ...
    if (publicOutput > 0) {
        address outputOwner;
        assembly {
            outputOwner := mload(add(proofDataPtr, 0x160))
        }
        withdraw(publicOutput, outputOwner, assetId); // <-- VULNERABILITY
    }
}

Why It’s Vulnerable

Aztec’s public post-incident explanation of the old immutable bridge described a claim-proof bug: the proof system did not correctly bind the victim’s final output note to the user who should receive it. As a result, a malicious sequencer or escape-hatch caller could present a proof for a victim’s claim while replacing the final output owner with an attacker-controlled address. The legacy bridge trusted verifier success alone, then executed the public L1 withdrawal encoded in the proof data.

That is exactly what the transaction shows. The submitted escapeHatch proof had rollupSize = 0, which the contract treated as one escape-hatch transaction, and its only public record had publicInput = 0, publicOutput = 1158 ETH, assetId = 0, and outputOwner = 0x6952d9246e9afe8b887b2877225163436f78e97f. Once verify(bytes,uint256) returned successfully, processDepositsAndWithdrawals(...) reached withdraw(...) and released 1158 ETH to the attacker address without any additional public ownership check at the bridge layer.

Attack Execution

High-Level Flow

  1. The attacker EOA 0x6952d9246e9afe8b887b2877225163436f78e97f called legacy RollupProcessor.escapeHatch(bytes,bytes,bytes).
  2. The contract checked getEscapeHatchStatus() and the escape-hatch window was open at block 25339094.
  3. The submitted proof header encoded rollupId = 4487, rollupSize = 0, and one public transaction.
  4. That public transaction encoded a 1158 ETH public output with outputOwner = 0x6952d9246e9afe8b887b2877225163436f78e97f.
  5. The verifier call succeeded, the bridge advanced its roots, and withdraw(...) sent 1158 ETH from bridge custody to the attacker.

Detailed Call Trace

The top-level call was:

  • 0x6952d9246e9afe8b887b2877225163436f78e97f -> 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba
  • selector 0xd1c65264 (escapeHatch(bytes,bytes,bytes))

Inside escapeHatch(...), the bridge called the verifier:

  • 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba -> 0x48cb7ba00d087541dc8e2b3738f80fdd1fee8ce8
  • selector 0xac318c5d (verify(bytes,uint256))

The proof header decoded to:

  • rollupId = 4487
  • rollupSize = 0
  • dataStartIndex = 938112
  • numTxs = 1

For the only processed public transaction, the bridge saw:

  • proofId = 0
  • publicInput = 0
  • publicOutput = 1158 ETH
  • assetId = 0 (ETH)
  • outputOwner = 0x6952d9246e9afe8b887b2877225163436f78e97f

After verifier success, the trace shows the only value-bearing internal transfer:

  • 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba -> 0x6952d9246e9afe8b887b2877225163436f78e97f
  • 1158 ETH

No ERC-20 transfers occurred in this transaction. The drain was a single public ETH withdrawal.

Financial Impact

Confirmed on-chain impact in this transaction:

  • 1158 ETH transferred from the legacy bridge to the attacker EOA.

The bridge’s ETH balance fell from 1158.759804206229645062 ETH before the call to 0.759804206229645062 ETH after the call. Public alerts estimated the loss at roughly $2.15M, which is consistent with the 1158 ETH outflow at incident-time prices.

Evidence

  • Transaction: 0xab306cd2184d23b6ba3e151b10b3b9a0b81f211cc16f4f3b0c79f0b17a59c2b5
  • Block: 25339094
  • Timestamp: 2026-06-17T18:34:47Z
  • Attacker EOA / payout recipient: 0x6952d9246e9afe8b887b2877225163436f78e97f
  • Legacy vulnerable bridge: 0x737901bea3eeb88459df9ef1be8ff3ae1b42a2ba
  • Verifier reached by trace: 0x48cb7ba00d087541dc8e2b3738f80fdd1fee8ce8
  • Escape-hatch status at the exploit block: open, with 106 blocks remaining in the current window
  • Proof header fields: rollupId = 4487, rollupSize = 0, numTxs = 1
  • Public record fields: publicOutput = 1158 ETH, assetId = 0, outputOwner = 0x6952d9246e9afe8b887b2877225163436f78e97f
  • Balance delta: bridge 1158.759804206229645062 ETH -> 0.759804206229645062 ETH

Remediation

  • Retire or permanently disable immutable legacy bridge contracts that still expose escape-hatch claim processing.
  • Ensure proof circuits bind the final output note and public withdrawal recipient to the rightful claimant, not only to a valid note/nullifier transition.
  • Treat verifier success as necessary but insufficient for custody release; the L1 processor should separately enforce recipient binding for escape-hatch claims.
  • Add explicit invariant tests for escape-hatch proofs that try to substitute attacker-controlled output owners for victim-controlled notes.