# zkLink Nova bridge exploit (~$780K): uncleared transient hash in Arbitrator.claimMessage() enables forged batch root and escrow drain

> On 2026-10-28 22:21 UTC a single Ethereum transaction drained zkLink Nova's Ethereum escrows of 22.43 ETH and 11 ERC-20 tokens (about $780K depending on the pricing source). The attacker abused Arbitrator.claimMessage(), which runs a caller-supplied target and calldata and never clears its transient finalizeMessageHash slot, together with the publicly callable SyncL2TxHashRelayer, to install a forged batch root.

- **Published:** 2026-10-31T12:54:18Z
- **Last reviewed:** 2026-10-31T12:54:18Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-4903
- **Card image:** https://intel.threadlinqs.com/og/threat/TL-2026-4903.png
- **ID:** TL-2026-4903
- **Severity:** HIGH
- **Category:** VULNERABILITY
- **Status:** MONITORING
- **Detections:** 9 (1 shown free below) · **IOCs:** 7

## Description

zkLink Nova is an aggregated Layer 3 zkEVM that uses an Arbitrator contract on Ethereum to relay sync hashes and batch roots between its primary chain and secondary-chain gateways (Linea, Optimism, Base, Blast, Mantle and others). Per zkLink documentation, the SyncL2TxHashRelayer periodically triggers secondary-chain contracts to send sync hashes through canonical bridges, the Arbitrator forwards them to the primary-chain gateway, and the batch root returns the same way to enable withdrawals.

The reported root cause (DEV Community analysis by panda-security; SlowMist as quoted by trade press) is two flaws in Arbitrator.claimMessage(). First, the function executes a caller-supplied target and calldata as the Arbitrator, and gateways trust msg.sender == Arbitrator. Second, the transient-storage validation hash finalizeMessageHash is never cleared, so once a genuine message arms it, it stays valid for later claims with matching parameters in the same transaction.

Exploit chain as reported: (1) the attacker called the publicly callable SyncL2TxHashRelayer with a legitimate pending Linea message, so the Linea bridge verified it and finalizeMessageHash was set; (2) in the same transaction the attacker called the relayer again, setting the canonical message service to EthereumGateway with calldata wrapping syncBatchRoot(116409, attackerChosenRoot, 0), which the gateway accepted because msg.sender was the Arbitrator; (3) EthereumGateway never called receiveMessage, so the slot stayed uncleared and the stale hash matched the recomputed one; (4) the attacker submitted 12 permissionless withdrawal proofs against the forged root and drained the escrows to their own address. Blockaid reported that a new batch root was recorded before the withdrawal events in the same transaction, leaving about 0.001 ETH in the ETH escrow.

The drain was 22.43 ETH plus pufETH 245.7379, USDC 18,113.45, ezETH 12.5693, rsETH 6.8665, STONE 6.2998, USDT 3,360.90, mstETH 2.6372, mswETH 2.4826, rswETH 0.5, wstETH 0.4660 and WBTC 0.1736. Valuations range from $743K to $805K; BlockSec Phalcon reported about $780K, SlowMist $783.1K, Blockaid about $786K, and Hokanews $778,625.84. Follow-on attacks were reported on Optimism, Base, Blast and Mantle; Parapet Labs attributed a second wave (about 24.76 ETH plus tokens, from 07:16 UTC on 2026-10-29) to a different actor. Secondary-chain pauses blocked deposits but not withdrawal finalization. zkLink suspended block production, patched the contracts and disabled withdrawals at about 13:08 UTC on 2026-10-29, and offered a 20% white-hat bounty with no further action if funds are returned. No CVE has been assigned. The SlowMist original could not be fetched (HTTP 403), so SlowMist statements here come from secondary reporting.

## MITRE ATT&CK

- T1587.004 Exploits
- T1190 Exploit Public-Facing Application
- T1565 Data Manipulation
- T1657 Financial Theft

## Free detection (Splunk SPL, Microsoft KQL, Sigma)

**zkLink Arbitrator Stale finalizeMessageHash Reuse — Forged Batch Root** (`TL-2026-4903-SPL-001`)

Per-transaction detection of the TL-2026-4903 exploit core: the Arbitrator relays syncBatchRoot to a gateway while claimMessage calls outnumber genuine receiveMessage calls, meaning a stale transient finalizeMessageHash was reused. Also hits the known attacker addresses and the exploit transaction hash.

- **Languages:** Splunk SPL, Microsoft KQL, Sigma
- **ATT&CK:** T1190, T1211, T1565, T1565.001, T1565.002, T1068
- **Data sources:** ethereum_traces (index=blockchain sourcetype=ethereum:traces)
- **Severity:** critical
- **Confidence:** high

### Splunk SPL

````spl
index=blockchain sourcetype="ethereum:traces" chain="ethereum" earliest=-24h
```Flag transactions where the zkLink Arbitrator relays syncBatchRoot with more claimMessage calls than genuine receiveMessage calls (stale finalizeMessageHash reuse, TL-2026-4903)```
| eval from_a=lower(from_address), to_a=lower(to_address)
| eval is_claim=if(to_a="0x1ee09a2caa0813a5183f90f5a6d0e4871f4c6002" AND method_name="claimMessage",1,0)
| eval is_root=if(from_a="0x1ee09a2caa0813a5183f90f5a6d0e4871f4c6002" AND method_name="syncBatchRoot",1,0)
| eval is_recv=if(method_name="receiveMessage",1,0)
| eval ioc_hit=if(in(from_a,"0xaca0655a11200a80d33d0e48786317304cde5718","0xd5f162b68a0281686e8dc76db8d67d2f3e6aff07","0xdab137f390df77caa97fd492540e973fd5e2f8c8") OR lower(tx_hash)="0x4f3c23e75b9134e8cae5a462550daad42fbbc19242567fcc34c0a035e297462c",1,0)
| stats sum(is_claim) as claim_calls sum(is_root) as root_calls sum(is_recv) as receive_calls max(ioc_hit) as ioc_hit min(_time) as first_seen values(from_a) as callers by tx_hash block_number
| where (root_calls>=1 AND claim_calls>receive_calls) OR ioc_hit=1
| eval first_seen=strftime(first_seen,"%Y-%m-%d %H:%M:%S")
| table first_seen tx_hash block_number callers claim_calls root_calls receive_calls ioc_hit
````

### Microsoft KQL

```kql
let arbitrator = "0x1ee09a2caa0813a5183f90f5a6d0e4871f4c6002";
let ioc_addrs = dynamic(["0xaca0655a11200a80d33d0e48786317304cde5718","0xd5f162b68a0281686e8dc76db8d67d2f3e6aff07","0xdab137f390df77caa97fd492540e973fd5e2f8c8"]);
EthereumTraces_CL
| where TimeGenerated > ago(24h) and chain == "ethereum"
| extend from_a = tolower(from_address), to_a = tolower(to_address)
| summarize claim_calls = countif(to_a == arbitrator and method_name == "claimMessage"),
            root_calls = countif(from_a == arbitrator and method_name == "syncBatchRoot"),
            receive_calls = countif(method_name == "receiveMessage"),
            ioc_hit = max(iff(from_a in (ioc_addrs) or tolower(tx_hash) == "0x4f3c23e75b9134e8cae5a462550daad42fbbc19242567fcc34c0a035e297462c", 1, 0)),
            first_seen = min(TimeGenerated), callers = make_set(from_a) by tx_hash, block_number
| where (root_calls >= 1 and claim_calls > receive_calls) or ioc_hit == 1
| project first_seen, tx_hash, block_number, callers, claim_calls, root_calls, receive_calls, ioc_hit
```

### Sigma

```yaml
title: zkLink Arbitrator syncBatchRoot Relayed Without receiveMessage
id: 3b8e1c52-6f0a-4d7b-9a41-2c5d8e7f1a03
status: experimental
description: Detects the zkLink Nova Arbitrator invoking syncBatchRoot on a gateway through claimMessage, the forged batch root installation of TL-2026-4903. Transaction-level correlation of claimMessage versus receiveMessage counts is done in the SPL and KQL forms; this rule is the per-trace pre-filter.
references:
  - https://dev.to/pandasecurity/zklink-nova-exploit-a-stale-transient-hash-forged-a-root-oji
  - https://intel.threadlinqs.com
author: AII-Detector — ThreadLinqs Intelligence
date: 2026-10-31
tags:
  - attack.initial-access
  - attack.t1190
  - attack.defense-evasion
  - attack.t1211
  - attack.impact
  - attack.t1565.001
  - attack.t1565.002
  - attack.privilege-escalation
  - attack.t1068
logsource:
  product: ethereum
  service: traces
detection:
  selection_arb:
    from_address:
      - '0x1ee09a2caa0813a5183f90f5a6d0e4871f4c6002'
    method_name: 'syncBatchRoot'
  selection_ioc:
    from_address:
      - '0xaca0655a11200a80d33d0e48786317304cde5718'
      - '0xd5f162b68a0281686e8dc76db8d67d2f3e6aff07'
      - '0xdab137f390df77caa97fd492540e973fd5e2f8c8'
  condition: selection_arb or selection_ioc
falsepositives:
  - Legitimate zkLink Arbitrator relays of syncBatchRoot following a canonical bridge receiveMessage
  - Contract upgrade or migration testing on forks
level: high

```

**Known false positives:**

- Legitimate Arbitrator syncBatchRoot relays that follow a canonical bridge receiveMessage in the same transaction
- Forked-mainnet exploit replays by researchers

**Tuning notes:** Verify the trace ETL emits receiveMessage frames from canonical bridges; if it only emits top-level calls, count the bridge message events instead. Keep the Arbitrator address list current after the 2026-10-29 patch redeploys. Assumes a blockchain-ETL feed (node debug traces, decoded logs, transactions) with fields chain, tx_hash, block_number, from_address, to_address, method_name, inner_method_name, inner_target, event_name, token_address, value_eth; map names to your schema. KQL uses custom tables EthereumTraces_CL, EthereumLogs_CL, EthereumTransactions_CL.

### 8 more detection rules (Splunk SPL, Microsoft KQL, Sigma), unlock with TL Creds

- zkLink Arbitrator Multiple claimMessage Calls in One Transaction (locked: unlock with TL Creds)
- zkLink Forged Batch Root Followed by Escrow Outflow in Same Transaction (locked: unlock with TL Creds)
- zkLink Follow-On Forged Batch Root Attempts on Secondary Chains (locked: unlock with TL Creds)
- zkLink claimMessage Wrapping syncBatchRoot (Forged Batch Root Mechanism) (locked: unlock with TL Creds)
- zkLink Nova Exploit Transaction and Victim Contract Interaction (locked: unlock with TL Creds)
- zkLink Exploit Contract Deployment and Staging by Attacker EOA (locked: unlock with TL Creds)
- zkLink Escrow Multi-Token Drain in One Transaction (locked: unlock with TL Creds)
- zkLink Exploit Actor Address Activity Across Chains (locked: unlock with TL Creds)

Sign up free to unlock the detection rules: https://threadlinqs.com/?modal=signup

## Sources

- [zkLink Nova Exploit: A Stale Transient Hash Forged a Root](https://dev.to/pandasecurity/zklink-nova-exploit-a-stale-transient-hash-forged-a-root-oji)
- [zkLink Nova Goes Offline After Hacker Steals More Than $780,000 From Its Bridge - Unchained](https://unchainedcrypto.com/zklink-nova-goes-offline-after-hacker-steals-more-than-780000-from-its-bridge/)
- [zkLink Nova Bridge Loses $786K After Attacker Forges Withdrawal Data - Blockfence](https://blockfence.io/zklink-nova-bridge-loses-786k-after-attacker-forges-withdrawal-data/)
- [zkLink Nova Bridge Exploited for ~$786K as Forged Batch Root Drains Ethereum Escrow - The Crypto Times](https://www.cryptotimes.io/2026/10/29/zklink-nova-bridge-exploited-for-786k-as-forged-batch-root-drains-ethereum-escrow/)
- [zkLink Nova Bridge Exploit Drains $778,625 in ETH and Tokens in One Transaction - Hokanews](https://www.hokanews.com/2026/10/zklink-nova-bridge-exploit-drains.html)
- [zkLink Reports $783K Loss Due to Exploit, Says SlowMist - Coinfomania](https://coinfomania.com/zklink-reports-783k-loss-due-to-exploit-says-slowmist/)
- [zkLink docs: In-Detail Multi-Chain State Synchronization](https://docs.zklink.io/architecture/settlement-layer/in-detail-multi-chain-state-synchronization)
- [zkLink Official statement on X](https://x.com/zkLink_Official/status/2115731883313254565)

## Full data

The other 8 detection rules (Splunk SPL / Microsoft KQL / Sigma) unlock with TL Creds; a free account includes daily creds: https://threadlinqs.com/?modal=signup
Indicator values unlock with TL Creds, at a lower price on Red and Purple; AI agents can query rules and IOCs through the Threadlinqs MCP server (Purple): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-4903
