Technique · OAK-T10.002.001 · emerging
OAK-T10.002.001 — Off-chain Observer Source-Event Forgery
Description
A sub-class of T10.002 in which the verification predicate that fails sits in an off-chain observer — a relayer, filler, solver, or indexer — that decides a source-chain event occurred and, on the strength of that decision, causes value to be released on the destination side. The shared T10.002 primitive holds exactly: value is released against a message that should have been rejected. What separates the sub-class is where the broken check lives and who reviews it.
Every canonical T10.002 anchor before 2026 is an on-chain contract predicate: Wormhole's unvalidated guardian account (2022-02), Nomad's 0x00 trusted root (2022-08), Verus's missing source-amount conservation check (2026-05). Those defects sit in deployed bytecode, are in scope for every contract audit, and are visible to anyone who reads the verifier. T10.002.001 defects sit in Rust, Go, or TypeScript infrastructure that receives indexer-grade review while carrying contract-grade authority. Nobody audits the relayer with the rigour applied to the contract it instructs, and the relayer's decision is what actually moves the money.
Two sub-shapes are anchored, and the distinction is worth keeping because the fixes differ:
- (a) Parse-level forgery. The observer reconstructs an event from raw chain data and never validates that the data it parsed is genuinely that event. Across (2026-07): the relayer did not check the 8-byte Anchor event discriminator when rebuilding deposit events from Solana transaction traces, so transactions that moved no funds emitted data that parsed as valid deposits — 1,627 forged deposits across 18 destination chains at ~$41.7M face value, 581 filled, ~$4.5M realised. The fix is a type/identity check on the parsed record.
- (b) Semantic (custody-level) forgery. The observer validates the fields of a well-formed message but never validates the fact the message asserts. Coreum–XRPL (2026-08): relayers confirmed the transaction succeeded, read the amount and recipient from a memo, found them well-formed, and released XRP — without ever checking whether XRP had reached the bridge's XRPL account. The fix is a state read: reconcile custody balance before release.
Both shapes share the property that makes the class dangerous: the attacker authors the data the observer reads, and no capital, privilege, or cryptographic weakness is required. The cost of exploitation collapses to transaction fees, which is why the class produces near-total reserve depletion so reliably — nothing rations the attempt.
Substrate note for shape (a): Solana has no canonical event system. Events are reconstructed from transaction traces, and failed transactions still emit data. Every Solana event consumer inherits this hazard, and the discriminator check is the mitigation. Shape (b) is substrate-independent — it appears wherever a bridge lets user-supplied metadata (memos, calldata, tags) stand in for a statement about custody.
Observed indicators
- An off-chain component (relayer, filler, solver, indexer, executor) is the decision-maker for value release, while the on-chain contracts merely execute what it instructs.
- The observer's input is user-authored: a memo field, calldata, an emitted log, a trace record, or any structure the counterparty controls the contents of.
- Value release is keyed to an event ("a deposit happened") rather than to state ("the custody balance increased by X").
- Destination-side payouts occur with no matching source-side custody increase — the bridge's locked/issued invariant drifts while every individual payment looks legitimate.
- Repeated, same-shaped outbound payments from a bridge account within a short window, against a monotonically falling reserve.
- Source-side transactions that moved no funds (self-transfers, failed transactions, zero-value calls) preceding destination-side fills.
Detection signals
- Locked-versus-issued reconciliation in the release path — the single highest-value signal. Before releasing X, confirm the custody account actually holds at least X more than at the last reconciliation point. Both anchors are caught by this check at payment number one.
- Custody-account balance monitoring, not message-queue monitoring. In the Coreum case the drain ran 97 minutes across 94 payments against a visibly falling reserve; the message stream looked normal throughout.
- Discriminator / type-identity assertions on every parsed event record (Solana Anchor discriminators, ABI event topic hashes, protobuf type URLs). A parsed record whose type tag is absent or wrong is a forgery attempt, with essentially no legitimate population.
- Source-transaction sanity: a deposit event whose originating transaction transferred no value, or failed, should never produce a fill.
- Fill-rate and per-window payout caps on the observer's authority — a bridge that can empty its own reserve inside two hours has no brake, independent of what tripped the accelerator.
- Divergence between two independent observers. Running a second implementation that must agree before release converts a single parsing or semantic bug into a liveness failure rather than a loss.
Real-world examples
examples/2026-07-across-solana-relayer-anchor-event-discriminator-forgery.md— Across / Risk Labs on Solana — 2026-07-17 — ~$4.5M realised (canonical parse-level anchor, sub-shape (a)). The off-chain relayer reconstructed deposit events from Solana transaction traces without verifying the 8-byte Anchor event discriminator; transactions moving no funds emitted data parsing as valid deposits. 1,627 forged deposits across 18 destination chains at ~$41.7M face value, of which 581 were filled. The bridge contracts were not defective — the code that decided what the contracts should do was.examples/2026-08-coreum-xrpl-bridge-forged-deposit-memo-relayer-verification.md— Coreum–XRPL bridge — 2026-08-09 — 199,916 XRP (canonical semantic-level anchor, sub-shape (b)). Relayers validated transaction success, memo amount, and memo recipient, but never that XRP had reached the bridge's XRPL account. The attacker moved wrapped Coreum tokens between wallets they already controlled, attached a memo shaped like a genuine deposit, and drew real XRP out across 94 multisig-authorised payments in 97 minutes — leaving 493.5 XRP of a ~200,410 XRP reserve, a 99.75% depletion. The multisig signers were honest and the XRP Ledger was not compromised; the authorisation was genuine and the premise was false.
Pipeline anchors
- Intents-based fillers and solvers making value decisions from parsed chain events are the fastest-growing population carrying this shape. Contributors encountering a filler, solver, or executor that paid out against an event it reconstructed rather than a state it read should map the case here.
examples/2026-07-layerzero-executor-multi-chain-simultaneous-wallet-drain.mdis not an instance — the Executor's gas float was drained by key compromise (T11.011), not by a forged source event — but it is the adjacent role to watch, since executors decide when messages are delivered.
Reference implementations
mg-detectors-rs— coverage gap. The failing predicate is off-chain, so on-chain detector coverage does not reach it; the reusable control is a reconciliation invariant inside the operator's own release path.- Anchor (Solana) — event discriminators are the type-identity primitive that shape (a) requires consumers to check; the framework emits them, and nothing forces consumers to verify them.
- Proof-of-reserves / locked-versus-issued tooling — normally deployed as a periodic public report; this class argues for the same computation inside the hot release path rather than as an after-the-fact attestation.
Mitigations
- Release on state, not on events (highest-leverage). The observer must confirm the asserted fact against custody state — read the balance, confirm the increase — before authorising anything. This closes both anchored sub-shapes.
- Type-identity checks on every parsed record. Verify Anchor discriminators, ABI topic hashes, or equivalent type tags before treating parsed data as the event it resembles. Treat all user-supplied metadata (memos, calldata, tags) as hostile input usable only to look up facts, never to establish them.
- Per-window and per-reserve-fraction payout caps on observer-driven release, so that a verification failure produces a partial and observable loss rather than a total one.
- Observer redundancy: require agreement between independently implemented observers before release, converting a parsing or semantic defect into a halt instead of a drain.
- Audit scope that follows authority, not language. If off-chain code can cause value to move, it belongs in the same review tier as the contracts — the review gap, not the bug, is what makes this class recur.
Citations
- See
examples/2026-07-across-solana-relayer-anchor-event-discriminator-forgery.mdandexamples/2026-08-coreum-xrpl-bridge-forged-deposit-memo-relayer-verification.mdfor the full citation sets.
Discussion
The reason this sub-Technique exists rather than being folded into T10.002 is that the parent's mitigation set points at the wrong artefact. T10.002's controls are contract-side: audit the verifier, validate the proof, bind the message. Apply them to Across or Coreum and you audit code that was correct. The bridge contracts in both incidents did exactly what they were told; what failed was the thing doing the telling. Naming the sub-class keeps the actual fix — reconcile custody state before release, and check the type tag on anything you parsed — attached to the incidents that need it.
The second reason is a governance observation about review tiers. Protocol teams have internalised that contracts get audits, formal methods, bug bounties, and multi-party review. The relayer is treated as infrastructure: it gets code review, tests, and monitoring appropriate to an indexer. But the relayer holds contract-grade authority — in Coreum it instructed a multisig, in Across it instructed fills across 18 chains. Authority, not implementation language, should determine review tier. Until that is standard practice, expect this class to keep producing incidents in which every cryptographic control held and the money left anyway.
Both anchors also share an economic property worth recording: exploitation was free. No flash loan, no capital, no privileged position, no compromised key — self-transfers and a memo in one case, zero-value transactions in the other. Classes whose exploitation cost approaches zero do not get rationed by attacker economics, and they tend to run until someone notices rather than until the attacker's capital is exhausted. That is why the bounding controls (payout caps, reserve-fraction limits) matter here even more than in key-compromise classes, and why 99.75% reserve depletion is the outcome to plan for rather than a tail case.
Promotion to stable awaits a third independent anchor, ideally from the intents/solver population, where the observer's authority is largest and the review gap is widest.
Parent Tactics
Worked examples (2)
- Coreum–XRPL bridge — relayers verified that a deposit message was well-formed and never that the XRP had arrived, so self-transfers carrying a forged memo released 199,916 real XRP — Coreum / XRP Ledger — 2026-08-09
- Across Protocol — a missing 8-byte Anchor discriminator check lets an attacker forge Solana deposit events that never moved funds, and the relayer fills 581 of them — Across / Risk Labs (Solana) — 2026-07-17