Callback-Origin Authentication Failure / Fake-Pool Settlement Spoof
- Name: callback-origin authentication failure / fake-pool settlement spoof
- Category: exploit pattern / callback-authenticity failure / approval-funded settlement abuse / route-binding failure
- Summary: These exploits happen when a protocol correctly expects a callback-capable settlement path to exist, but authenticates the callback sender too weakly. Instead of proving
this exact canonical pool / pair / market for this exact in-flight stage is calling back, the protocol proves something looser:the caller looks like a Uniswap-style pair,msg.sender matches the last pool I touched, orsome pool chosen by route bytes is now allowed to settle. If the same path can also spend wallet approvals, resolver inventory, or protocol balances, the callback itself becomes the theft primitive. Primitive Finance, Sushi RouteProcessor2, and ParaSwap Augustus V6 preserve the family cleanly: each trusted a spoofable settlement counterparty strongly enough that real tokens moved in service of a fake debt. - What it is:
- The protocol integrates an AMM, flash-swap, or other callback-driven settlement path and expects a later callback to ask for payment.
- The callback handler should authenticate one exact counterparty for one exact route stage: canonical pair or pool, canonical market, canonical payer source, and canonical settlement meaning.
- Instead, the handler authenticates a weaker object such as
looks like a pair,matches lastCalledPool,router said so, oraddress selected in route bytes. - The attacker supplies or controls a counterfeit pool, pair, market, or callback-capable contract that satisfies the weaker test.
- The protocol then honors a fabricated settlement obligation and pays with real user-approved tokens, router inventory, or protocol-controlled funds.
- Typical preconditions:
- A callback-driven swap or settlement path exists, commonly Uniswap v2
uniswapV2Call(), Uniswap v3uniswapV3SwapCallback(), or a protocol-specific hook. - The callback logic can move real value if accepted: wallet allowance consumption, router inventory payment, flash-swap repayment, or balance release.
- The protocol does not re-derive the canonical callback sender from trusted state such as factory, token ordering, fee tier, route stage, or precommitted pool identity.
- The attacker can deploy or control a counterfeit callback counterparty, or can steer route bytes toward one.
- Downstream settlement semantics still trust attacker-shaped payer, token, amount, or market context once callback admission succeeds.
- A callback-driven swap or settlement path exists, commonly Uniswap v2
- Common vulnerable surfaces:
- routers that read pool addresses directly from user- or solver-supplied route bytes
- direct Uniswap v3 integrations that optimize around the canonical router but do not recompute the expected pool at callback time
- flash-swap helpers that check only
looksLikePairorfactory says this is a pairwhile leaving the actual route / market context attacker-controlled - settlement adapters that authenticate callback caller but not which payer, inventory bucket, or market object the callback is allowed to charge
- approval-funded paths where dormant wallet allowances quietly turn a spoofed settlement callback into immediate theft
- Exploit sequence:
- The attacker identifies a callback-driven settlement path that can move real value once the callback is accepted.
- They create or control a counterfeit pool, pair, option market, or callback-capable contract whose interface matches what the protocol expects.
- They steer the protocol into interacting with that counterfeit counterparty, often through route bytes, fake market objects, or a direct pool parameter.
- The counterfeit counterparty invokes the settlement callback with attacker-shaped payer, token, amount, or market context.
- Because callback authentication only proves a weak origin story, the protocol accepts the callback as legitimate settlement for the in-flight route.
- The protocol transfers real tokens, consumes victim allowance, or releases real inventory to satisfy a debt the attacker fabricated.
- The attacker exits through the counterfeit pool / market structure holding the real asset while victims or the protocol are left with worthless paper or missing balances.
- Variants:
- Attacker-chosen last-called pool — the protocol stores a pool address chosen from route bytes, then later treats
msg.sender == storedPoolas proof of legitimacy. Sushi RouteProcessor2 is the canonical exemplar. - Canonical-factory check without canonical-route check — the protocol proves
this is some real pair from some real factory, but never proves it is the exact pair the route and asset semantics required. - Direct-pool optimization without pool recomputation — a gas-optimized direct integration skips safer upstream routing layers and trusts callback context more than the original router would. ParaSwap Augustus V6 preserves this variant cleanly.
- Counterfeit market plus counterfeit pair — the callback sender is only one half of the problem; the downstream market object defining settlement semantics is counterfeit too. Primitive Finance is the best mixed exemplar.
- Approval-funded fake settlement — the protocol itself is not the treasury victim; dormant user allowances are the balance sheet that the fake callback spends against.
- Attacker-chosen last-called pool — the protocol stores a pool address chosen from route bytes, then later treats
Three callback-origin promotions healthy systems should not collapse
-
Did some callback-capable settlement counterparty reach us at all?
- This is the weak first question many integrations answer: some pool-like, pair-like, or market-like contract called the callback with plausible fields.
- Many systems stop here and quietly treat
a callback arrived from something AMM-shapedas if the real authentication work were done.
-
Did that counterparty prove it was the exact route-canonical pool / pair / market for this exact stage?
- This stronger question is whether the callback came from the one counterparty implied by trusted factory constraints, token ordering, fee tier, market ID, and the precommitted route stage.
- Sushi RouteProcessor2, Primitive, and ParaSwap Augustus V6 all preserve different ways this second promotion failed.
-
Did that accepted counterparty still deserve this payer, token-ordering, and settlement authority?
- Even a real pool for the right stage should not automatically inherit broad rights to choose who pays, what token semantics apply, or which inventory bucket gets charged.
- The recurring mistake is not only weak origin authentication, but the later quiet promotion from
this callback source is real enoughtothis callback may now define the spend story.
- Detection heuristics:
- Search for callback handlers that trust
msg.sender,lastCalledPool,onlyPool, orfactory.getPair(...)and ask whether those checks prove the exact pool for the exact route stage or merely a plausible callback source. - Trace where payer identity, token ordering, fee tier, and market identity come from during callback settlement. If any of them arrive from attacker-shaped route bytes or callback data after control has already left, the route is suspect.
- Treat every path that can spend wallet approvals on callback as custody-critical even if the protocol itself does not hold funds.
- If the protocol lets untrusted bytes select the pool first and authenticates the callback against that same selected address later, assume the check is circular until proven otherwise.
- Review whether the callback consumer recomputes the canonical pool from trusted state or merely accepts a decoded address, route suffix, or helper-provided pool claim.
- Search for callback handlers that trust
- Quick classifier against nearby families:
- Stay here when the decisive failure is
the protocol trusted the wrong callback sender / pool / pair / market counterparty to claim settlement authority. - Use callback-frame-authority-reuse when the callback source may be genuine but the protocol reused the wrong still-live in-flight frame across actor, target, stage, or payout bucket.
- Use reentrancy when the callback counterparty is not counterfeit and the core lesson is still unfinished-state reuse before accounting settled.
- Use arbitrary-call-token-approval-drain when the spender did not need a spoofed settlement callback at all and the real bug was an openly attacker-directed call surface over existing approvals.
- Use trusted-execution-envelope-corruption when the callback caller may be real but the protocol-authored settlement bytes later consumed downstream are what got corrupted or misparsed.
- Use account-confusion-root-of-trust-substitution when the earlier and sharper failure is that a fake market, jar, bank, or other root object was admitted as canonical before callback settlement logic even became the interesting seam.
- Stay here when the decisive failure is
What sound design looks like
-
Healthy systems keep three facts distinct on purpose:
some callback-capable counterparty exists,this exact route-canonical counterparty for this exact stage is the one calling back, andthat admitted counterparty still deserves only this narrow payer / token / settlement authority. -
The protocol can name one exact settlement counterparty before control leaves: canonical factory, canonical token ordering, canonical fee tier or market ID, canonical payer source, and canonical stage meaning all describe one callback object.
-
Callback admission is derived, not remembered. The handler recomputes the expected pool / pair / market from trusted local state written before the external call; it does not trust the same untrusted bytes that selected the counterparty in the first place.
-
Callback origin and settlement meaning stay fused. A real pool is not enough if the protocol still lets callback bytes redefine who pays, what asset is owed, or which market semantics govern the debt.
-
Wallet approval authority stays route-local. If user allowances back settlement, the protocol should still preserve one route-authenticated authority object binding spender, payer, canonical callback counterparty, and token semantics before any
transferFrom()becomes reachable. approval-funded-route-authority-truth-security-object is the sharper positive-design companion when the design question becomeswhat exact allowance-backed route object should have existed before callback settlement could spend a wallet? -
Direct-pool AMM integrations should recompute the exact callback counterparty and preserve the exact payer bucket before settlement can move value. amm-callback-counterparty-recomputation-and-payer-pinning-discipline is the sharper companion when the local design job is narrower than the whole route lifecycle:
which exact pool may callback, and which exact wallet / inventory sleeve may pay it? -
The settlement consumer trusts one explicit envelope. If the callback is accepted, later token movement still consumes one authenticated settlement object rather than free-form callback bytes. settlement-envelope-truth-security-object is the sharper companion when the next question becomes
what exact protocol-authored settlement object should downstream code have trusted? -
End-to-end routes commit their stages. A router, flash helper, or solver path should pin not only the callback counterparty but also how that callback stage may transition into payer- and token-moving settlement. route-commitment-across-callback-frame-and-settlement-envelope is the sharper companion when the system needs both stage-local callback truth and stage-local settlement truth.
-
The clean target is stronger than
the callback came from a real-looking pool: one exact counterparty, one exact payer source, one exact token meaning, one exact stage, consumed once. -
Defenses:
- Recompute canonical pool / pair / market identity at callback time from trusted state, factory constraints, token ordering, fee tier, and route stage.
- Record payer identity and stage meaning before the external call, then make the callback consume only that precommitted state.
- Keep route bytes, callback bytes, and market objects from deciding fresh pool identity, payer identity, or asset semantics after the route is already live.
- Treat allowance-backed callbacks as custody surfaces and bind spender, payer, counterparty, and token set into one route-local authority object.
- Fail closed on ambiguous or mixed-format callback context;
some bytes decodeis not enough. - Prefer typed route and settlement objects over loosely packed per-hop bytes that ask later code to reconstruct what the route supposedly meant.
Failure-prevention checklist
- Write down the exact callback identity object the protocol means to authenticate: factory, pool / pair / market address, token ordering, fee tier, route stage, payer bucket, and token semantics.
- Commit that identity in trusted local state before the external call and make the callback handler re-derive or verify against that committed object instead of against attacker-shaped route data.
- Require callback admission to prove both origin and meaning: not just
who called, butwhat debt this stage is allowed to settle,who may fund it, andwhich asset semantics apply. - Keep user approvals and router inventory route-local. A callback that passes one narrow stage check should not automatically gain authority over every allowance or balance the router can reach.
- Reject attacker-selected pool or market identities unless the protocol can independently prove they are the canonical counterparties for the approved route.
- Separate route planning from callback settlement. If the same bytes both choose the pool and later prove the pool, the authentication story is circular.
- Treat counterfeit but interface-compatible market objects as part of the callback threat model. A real pair plus fake market semantics can still turn settlement into theft.
- Fail closed on legacy callback formats, compatibility shims, or parser paths that let stage meaning drift while the caller still looks plausible.
- Fuzz adversarial cases where route bytes select a fake pool, callback data rebinds the payer, token ordering flips, counterfeit markets redefine semantics, or dormant approvals make a fake debt immediately payable.
- Give operators a narrow blast-radius response path: revocation campaigns, route disablement, pool-type disablement, and inventory caps should already assume the attacker may not need protocol treasury access to cause loss.
Audit prompts
- What exact object proves that this callback came from the canonical counterparty for this route stage rather than from a merely plausible pool or pair?
- Is canonical pool / pair / market identity recomputed from trusted state, or merely remembered from attacker-shaped route bytes?
- Where is payer identity committed before control leaves, and can callback data later substitute a different wallet or inventory bucket?
- If the callback is genuine, what still proves token ordering, fee tier, amount semantics, and market meaning belong to the same route stage?
- Can a fake but factory-valid pair, counterfeit option market, or attacker-created pool satisfy callback admission while still redirecting settlement?
- Which balances or approvals become reachable once the callback is admitted: route-local escrow only, or any user wallet that previously approved the router?
- Does the protocol authenticate one exact parser / layout / stage meaning for callback context, or merely accept any decoding path that yields plausible fields?
- If route bytes chose the pool, what independent fact later proves the chosen pool was actually canonical rather than just attacker-selected?
- What fails closed if the callback arrives from the wrong stage, with the wrong payer, or for the wrong asset ordering?
- If this path were publicized as vulnerable today, could responders narrow or disable just the callback-backed settlement surface, or would the only defense be a broad approval-revocation race?
Lessons that should already be standard
-
Some callback reached usis the beginning of review, not the end. Healthy systems keepcallback-capable counterparty exists,this exact route-canonical counterparty is calling, andthat admitted counterparty may spend from this exact bucket with this exact token meaningas separate proofs. -
A real callback-capable interface is not a trust proof.
Looks like a Uniswap poolandis the canonical pool for this exact route stageare very different claims. -
If untrusted bytes choose the pool and later the protocol authenticates the callback against that chosen pool, the check is circular.
-
Approval-backed settlement is custody infrastructure. Dormant wallet allowances turn small callback-authentication bugs into live wallet-drain incidents.
-
Callback origin and settlement meaning have to stay fused. The moment a callback can redefine who pays or what is owed, the protocol is already authenticating the wrong object.
-
Direct-integration gas optimizations deserve more paranoia, not less. Skipping a canonical router or helper often means inheriting all of its blast radius without inheriting its validation layers.
-
Counterfeit market objects and counterfeit callback counterparties compound each other. Primitive shows that a protocol can lose even when one layer of the story looks real enough.
-
Factory-validis weaker thanroute-valid. A real pair from a real factory can still be the wrong economic counterparty for the route the protocol thinks it is settling. -
The clean design question is blunt: which exact counterparty is entitled to ask this code to pay right now, from which exact bucket, for which exact stage, and how was that proven before control returned?
-
High-signal incidents:
- approval-drain whitehack (2021-02-22) — the connector trusted a Uniswap-style callback and attacker-supplied option context strongly enough to spend victim wallet approvals settling a counterfeit market.
- approval drain (2023-04-09) — user-supplied route bytes chose the pool, RouteProcessor2 stored that pool as
lastCalledPool, and the later callback check became circular, letting attacker-controlled pools drain approved wallets. - [[../../../incidents/callbacks-reentrancy/paraswap-augustus-v6-uniswapv3swapcallback-fake-pool-approval-drain-2024-03-20|ParaSwap Augustus V6
uniswapV3SwapCallback()fake-pool approval drain (2024-03-20)]] — a gas-optimized direct Uniswap v3 path did not keep one canonical pool / payer object pinned before callback settlement spent victim allowances.
-
Why this pattern matters:
- It preserves a callback-specific exploit family that otherwise gets flattened into either
just another approvals bugorjust reentrancy, losing the sharper lesson that the protocol paid the wrong settlement counterparty. - It also bridges exploit-side and design-side notes cleanly: the negative question is
who was wrongly trusted to callback and claim debt, while the positive follow-up is how a healthy route should pin callback truth and settlement truth before any allowance or inventory becomes spendable. - The family is increasingly important because modern routers, direct-pool integrations, and approval-funded helpers keep pushing settlement logic closer to wallet allowances and away from protocol escrow, which makes callback-authentication mistakes immediately user-facing.
- It preserves a callback-specific exploit family that otherwise gets flattened into either
Internal linkages
- Parent branch note: reentrancy — use the parent when the broader callback-control-flow family matters more than the counterfeit-counterparty variant.
- Adjacent stale-frame note: callback-frame-authority-reuse — use that note when the callback source is real but one still-live frame authenticated the wrong actor, target, or stage.
- Narrow AMM-integration companion: amm-callback-counterparty-recomputation-and-payer-pinning-discipline.
- Positive-design companion: settlement-envelope-truth-security-object.
- Route-authenticity companion: route-commitment-across-callback-frame-and-settlement-envelope.
- Allowance-backed companion: approval-funded-route-authority-truth-security-object.
- Adjacent approval-drain family: arbitrary-call-token-approval-drain.
- Adjacent settlement-corruption family: trusted-execution-envelope-corruption.
- Adjacent counterfeit-root-object family: account-confusion-root-of-trust-substitution.
- Best mixed counterfeit-market exemplar: primitive-finance-connectors-fake-option-flash-swap-callback-approval-drain-whitehack-2021-02-22.
- Best circular-last-called-pool exemplar: sushiswap-routeprocessor2-fake-pool-callback-approval-drain-2023-04-09.
- Best direct-pool-optimization exemplar: paraswap-augustus-v6-uniswapv3swapcallback-fake-pool-approval-drain-2024-03-20.
Control surface
-
The leverage sits where callback-capable settlement counterparties are admitted: direct-pool parameters, route bytes, remembered
lastCalledPoolstate, factory checks, market-object admission, payer rebinding, and which balances or allowances the callback is allowed to consume. -
The recurring mistake is layered: the protocol proves
some callback-capable counterparty exists, quietly promotes that intothis exact route-authenticated counterparty is the one I meant, and then quietly promotes that again intothis admitted counterparty may now choose or inherit the spend story. -
The most useful review question for this branch is blunt: if this callback asked us to pay right now, what exact locally committed fact proves it is the one counterparty we meant to owe, rather than merely a convincing impostor?
-
Sources:
- settlement-envelope-truth-security-object
- approval-funded-route-authority-truth-security-object
- route-commitment-across-callback-frame-and-settlement-envelope
- primitive-finance-connectors-fake-option-flash-swap-callback-approval-drain-whitehack-2021-02-22
- sushiswap-routeprocessor2-fake-pool-callback-approval-drain-2023-04-09
- paraswap-augustus-v6-uniswapv3swapcallback-fake-pool-approval-drain-2024-03-20
- https://www.sushi.com/blog/routeprocessor2-post-mortem
- https://primitivefinance.medium.com/postmortem-on-the-primitive-finance-whitehack-of-february-21st-2021-17446c0f3122
- https://veloradex.medium.com/post-mortem-augustus-v6-vulnerability-of-march-20th-2024-5df663a4bf01
-
Last reviewed: 2026-07-31 UTC