Primitive Finance Connectors Fake-Option Flash-Swap Callback / Approval-Drain Whitehack (2021-02-22)
- Name: Primitive Finance connectors fake-option flash-swap callback / approval-drain whitehack
- Date: 2021-02-22
- Protocol: Primitive Finance
- Chain: Ethereum
- Category: callback-origin authentication failure / token-approval abuse / fake-market context injection / whitehat rescue
- Loss: no reported net user loss; Primitive’s whitehat transactions rescued roughly 10M of potential exposure
- Summary: Primitive’s immutable v1 connector contracts let users open option positions through Uniswap v2-style flash swaps. But the connector callback
uniswapV2Call()only authenticated that the caller looked like a Uniswap/SushiSwap pair; it did not prove that the flash swap had been initiated through Primitive’s intended entry flow, that the callbacksendermatched Primitive’s own route initiator, or that the downstream option / pair context was canonical. An attacker could therefore deploy a fake redeem token, a malicious option contract, and an attacker-controlled pair, then use the callback to reachflashMintShortOptionsThenSwap()with victim and token parameters of their choice. Because users had previously granted the connectors large token approvals, the connector could pull realWETH,DAI, or other approved assets from victim wallets, mint them into worthless attacker-controlled option tokens, leave those worthless tokens with the victim, and route the real assets to the attacker. In approval-funded route terms, Primitive never kept one route-local authority object that fused the approved connector surface, canonical pair, canonical option market, victim payer, and settlement meaning before callback settlement spent wallet allowance. Primitive, Dedaub, Immunefi, and other responders used the same path in three whitehat transactions to rescue exposed wallets before returning the funds. - Trigger / entry point:
- Primitive’s connector path for flash-minted longs used
uniswapV2Call()as the settlement callback after a Uniswap v2 flash swap. - The downstream helper
flashMintShortOptionsThenSwap()was supposed to be reachable only through the connector’s intended self-call / dispatcher flow; Primitive’s own postmortem highlighted that the sensitive entrypoint was not directly public and relied on the callback-driven dispatcher to reach it. - The connector validated that the callback came from a Uniswap-style pair, but did not sufficiently authenticate the flash-swap initiator / callback
senderor bind the callback to a canonical Primitive-created route context. - The connector also trusted caller-shaped option / redeem / underlying context strongly enough that an attacker-controlled option contract could become the spending authority’s semantic root of truth.
- Primitive’s connector path for flash-minted longs used
- Root cause:
- Primitive treated
this callback came from a Uniswap-style pairas if it also meantthis is the exact flash-swap context Primitive itself meant to settle. - More specifically, the callback checked pair authenticity but not the initiator truth that Primitive’s own dispatcher path relied on, so attacker-controlled flash-swap setup could reach the self-call-gated mint helper with unrestricted economic context.
- The connector was an already-approved spender for user wallets, so any callback-authentication gap was automatically a wallet-drain risk rather than only a protocol-accounting bug.
- The option / market objects used inside settlement were not pinned to canonical Primitive markets; a malicious option contract could satisfy the connector’s expected interface while redirecting the real underlying tokens.
- In practice, the connector combined broad user token approvals, incomplete callback-origin / route binding, and attacker-supplied market objects into one theft primitive.
- Primitive treated
- Exploit path:
- The attacker identified wallets that had granted the vulnerable Primitive connector contracts large or infinite approvals for valuable tokens such as
WETHorDAI. - They deployed a fake redeem token, then a malicious option contract whose
underlyingTokenwas the real approved asset and whose mint logic forwarded received underlying tokens to the attacker. - They created an attacker-controlled Uniswap v2 / SushiSwap-style pair between the fake redeem token and the real approved token, financing the setup with flash liquidity if needed.
- The attacker initiated a flash swap from that pair and encoded parameters that would drive Primitive’s connector callback into
flashMintShortOptionsThenSwap()using the victim as the effective payer. - Because
uniswapV2Call()did not prove that the callback belonged to Primitive’s intended route context, the connector accepted the callback and began settlement with real tokens in hand or with authority to pull them. - The connector transferred the victim’s approved real tokens into the malicious option contract, which minted worthless option tokens and immediately forwarded the real underlying tokens to the attacker-controlled beneficiary.
- Worthless option tokens were delivered to the victim address, the pair was made whole, and the attacker was left with the victim’s real assets while the victim retained only unredeemable paper.
- Primitive’s war room later executed the same exploit path in three whitehat transactions to rescue exposed wallets and return the funds.
- The attacker identified wallets that had granted the vulnerable Primitive connector contracts large or infinite approvals for valuable tokens such as
- What the attacker needed to be true:
- Victims needed to have standing approvals to one of the vulnerable Primitive connector contracts.
- The callback path had to trust
msg.senderas a Uniswap-style pair without proving that the pair and route had been created by Primitive’s legitimate entry flow. - The connector needed to accept attacker-supplied option / redeem contracts strongly enough that fake market objects could define settlement behavior.
- The malicious option contract needed to be able to receive the underlying asset and forward it away before the fake market context collapsed.
- The attacker needed enough pair liquidity or flash liquidity to stage and settle the synthetic market during the transaction.
- Technical anatomy worth remembering:
- This was not a protocol-treasury drain. The endangered funds sat in user wallets that had previously approved Primitive’s connectors as spenders.
- The dangerous primitive was not a raw arbitrary call. It was a settlement callback that authenticated the pair context too weakly, then spent already-approved tokens while believing it was settling a legitimate options-opening flow.
- Primitive’s own postmortem is especially useful here because it makes the call chain explicit:
flashMintShortOptionsThenSwap()was meant to stay behind a self-call gate, butuniswapV2Call()became an attacker-steerable dispatcher because it did not verify the flash-swap initiator tightly enough. - The exploit also depended on attacker-supplied, interface-compatible option contracts. The connector did not just trust the callback sender too much; it trusted the downstream market objects too much as well.
- Primitive’s core contracts had been audited, but the vulnerable connector contracts were outside the OpenZeppelin audit scope noted in later analyses.
- The incident is a useful whitehat case study because the contracts were immutable and unpausable, so responders had to exploit the bug faster than a blackhat could.
Pattern tags
- Callback-origin authentication failure
- Approval-funded route authority truth
- Settlement-envelope truth
- root-of-trust substitution
- approval-backed wallet drain / whitehat rescue under immutable contracts
Why the exploit worked
- Primitive’s callback admission proved only
this callback comes from a Uniswap-style pair, notthis exact Primitive-authored flash-swap route is now entitled to settle against this exact payer and option market. - The self-call gate around
flashMintShortOptionsThenSwap()was therefore weaker than it looked: onceuniswapV2Call()accepted an attacker-shaped flash swap, the connector’s protected mint helper inherited attacker-controlled economic context. - The connector also never pinned one canonical option-market object before spending allowance. A malicious option contract could present the expected interface while redefining where the real underlying tokens ultimately went.
- User approvals turned the bug into live wallet custody failure. The callback did not need protocol reserves; it could satisfy the counterfeit settlement story by pulling real
WETH,DAI, or other approved assets straight from victim wallets. - Primitive’s own incident writeup and the Origin reconstruction line up on the same operational lesson: the flash swap, fake pair, and fake option contract were only staging; the decisive break was that callback-time plausibility had become sufficient proof of wallet-spend authority.
Three callback-origin and approval-exercise promotions this incident collapsed
- A callback-capable pair-like counterparty could reach
uniswapV2Call()at all.- Primitive’s callback proved only that
msg.senderlooked like a real Uniswap/Sushi-style pair. That was enough to admit an attacker-controlled flash-swap staging object into the settlement path. - Primitive’s own postmortem says the critical miss plainly:
uniswapV2Call()did not verify the flash-swap initiator, only that the callback indeed came from Uniswap.
- Primitive’s callback proved only that
- That plausible callback source was over-promoted into the exact Primitive-authored route and market context the connector meant to settle.
- Once the pair check passed, the connector behaved as if the callback belonged to the canonical Primitive dispatcher flow, even though the attacker controlled the flash-swap setup and could supply counterfeit option / redeem objects.
- This was the decisive route-authentication break:
some pair can callbackbecamethis exact pair and this exact option market define the debt Primitive owes right now.
- That accepted callback-and-market story still inherited live wallet-spend authority through dormant approvals.
- Users had already approved the connector for valuable tokens, so the counterfeit settlement story did not need protocol treasury funds; it could cash out directly against victim wallets.
- The final authority jump was from
the callback looks settleabletothis connector may now exercise real wallet allowance for this exact payer and token set, which is why a fake market context became an approval-funded drain rather than a harmless fake trade.
Detection and audit pivots
-
Trace every path from
uniswapV2Call()or equivalent callback handlers totransferFrom(), token pulls, or option / vault mint helpers, then ask what exact fact proves the canonical payer and canonical market were precommitted before control left. -
Treat
factory-valid pairandlooks like a Uniswap callbackas weak evidence. Recompute whether the specific pair, token ordering, and route initiator belong to one Primitive-authored market and one exact in-flight stage. -
Enumerate every interface-shaped market object the connector trusts after callback admission. If attacker-supplied option or redeem contracts can still define underlying-token semantics, the route has already lost canonical settlement meaning.
-
Review allowance blast radius as part of callback audits, not after them. A callback bug on an already-approved connector is a custody bug even if the protocol treasury itself holds no funds.
-
Simulate the exact mixed-counterfeit path preserved here: fake pair plus fake option market plus victim-approved wallet. If the system only defends against one counterfeit layer at a time, the route is still underbound.
-
For operations response, maintain wallet-approval inventories and revoke / disable runbooks ahead of time. Primitive’s immutable connector family turned disclosure into a race to zero approvals and whitehat rescue, not a patch-and-redeploy exercise.
-
Representative transactions and contracts:
- Vulnerable primary connector called out by Primitive:
0x9Daec8D56CDCBDE72abe65F4a5daF8cc0A5bF2f9 - Other vulnerable / impacted connector contracts listed by Primitive:
0x69e82d3696B21c200CEf654bc294CACaD01E1Fb3,0x3256EF5AFb05BbBeC063c16d1052517775E1e9B6,0x126d852A3150C53B851eB340ea54Dc379d62f27c,0x66fD5619a2a12dB3469e5A1bC5634f981e676c75,0xb026991da22f7D8F51550D5f99C39DdBc1c02089 - Whitehat rescue transactions:
0xa903a3b8e098e1e10f73070ef370b347950ef2a6c9037ce76e6a9944b8a05d39,0x9c511b8063780104895c68d08e7dbb1f1557c59e3e96bd14ef190d9329e33113,0xac9771a1dd347bf880b7db8c842ad2481cb69a7fae671e78267dc9e1e045d010 - Vulnerable callback surface highlighted by Primitive:
uniswapV2Call()on connector contract0x9daec8d56cdcbde72abe65f4a5daf8cc0a5bf2f9 - Vulnerable downstream settlement entry highlighted by Primitive:
flashMintShortOptionsThenSwap()on the same connector family
- Vulnerable primary connector called out by Primitive:
-
Response / mitigation:
- Dedaub reported the issue through Immunefi, after which Primitive formed a war room with Dedaub, Immunefi, ReviewsDAO, and later OpenZeppelin support.
- Primitive immediately changed the frontend so visible actions reset approvals to
0, published an emergency approval-reset page, and publicly warned users that wallet balances with standing approvals remained at risk. - Because the contracts were immutable and unpausable, the responders executed three whitehat rescue transactions against exposed wallets, then returned the rescued funds.
- Primitive said future remediation would include retiring the vulnerable connector flow, stricter approval policy, permit-based UX where possible, and pausability in future contract designs.
Why this incident matters
- It is an early, clean example of approval-drain risk hiding inside callback-authentication failure rather than inside a generic arbitrary-call router.
- It also preserves a useful mixed lesson: fake pool context and fake market-object context compounded each other. The connector authenticated neither layer tightly enough before spending user approvals.
- The case is especially valuable because no blackhat theft was needed to show the danger. The whitehat path demonstrates exactly how quickly dormant wallet approvals can become a rescue-or-race problem once a callback bug is public.
Failure-prevention checklist
- Treat self-call gates as incomplete unless the dispatcher path itself is route-authenticated.
only this contract may call the mint helperis not a defense if an attacker-controlled flash-swap callback can still steer the contract into making that self-call with arbitrary economic context. - Recompute and pin the canonical callback counterparty from trusted route state before honoring
uniswapV2Call(). The healthy proof is notthis looks like a real Uniswap/Sushi pair, butthis is the exact pair for the exact Primitive-authored option-opening route now in flight. - Bind initiator, payer, pair, and market semantics into one route-local authority object before any callback path can reach allowance-funded token pulls. Primitive needed one authenticated object tying together
who opened the flash swap,which wallet may fund settlement,which pair may callback, andwhich option market defines what those tokens mean. - Reject interface-compatible but attacker-supplied market objects unless the protocol can independently prove they are canonical Primitive markets. If an option contract can define underlying / redeem semantics after callback admission, settlement truth is already compromised.
- Treat dormant approvals as live custody exposure during callback review. Any path from callback admission to
transferFrom(victim, ...)or equivalent should be modeled as a wallet-drain surface, not as a benign integration helper. - Prefer narrower, revocable spending authority where possible: per-swap permits, expiring approvals, bounded allowances, or dedicated ephemeral settlement helpers reduce blast radius when a callback gate fails.
- Preserve an operational kill path for immutable callback spenders. If a connector cannot be paused or upgraded, the incident plan should already include approval-reset UX, wallet inventory, user outreach, and whitehat / rescue procedures before disclosure forces a race.
Audit prompts
- What exact trusted state proves that
uniswapV2Call()belongs to the canonical Primitive-authored route now in flight rather than to any factory-valid pair that can produce a plausible callback? - Can the callback authenticate the flash-swap initiator / dispatcher path, or does it only authenticate that
msg.senderis some real Uniswap-style pair? - If
flashMintShortOptionsThenSwap()is protected by a self-call gate, what exact invariant proves only the legitimate dispatcher can ever cause that self-call with attacker-uncontrollable parameters? - Which fields define the settlement object after callback admission: pair, option contract, redeem token, underlying token, victim payer, and payout beneficiary? Which of those fields can still be attacker-supplied after control has already left Primitive?
- Could a fake but interface-compatible option contract or redeem token redefine where real approved tokens go while still satisfying the connector’s expected interface?
- Once the callback is admitted, which balances become reachable: only route-local escrow, or any wallet that previously approved the connector for
WETH,DAI, or other assets? - What would fail closed if the pair were real but the option market were counterfeit, or if the option market were real but the callback came from the wrong pair or initiator?
- If these connectors were disclosed as vulnerable again, would the protocol have a faster containment path than mass allowance revocation plus whitehat rescue?
Lessons that should already be standard
- A self-call restriction is not route authentication. If an attacker can reach the dispatcher that performs the self-call, the protected helper is only as strong as the callback gate in front of it.
Factory-valid pairis weaker thanthe exact pair this route meant to trust. Primitive’s callback admitted a plausible settlement counterparty, not the canonical one.- Counterfeit callback origin and counterfeit market semantics compound each other. Authenticating only the pair while trusting attacker-supplied option objects still leaves the protocol paying a fake debt in a fake market.
- Approval-backed connectors are custody surfaces even when protocol reserves are untouched. Dormant wallet allowances turn callback-authentication mistakes into direct user-balance drains.
- Immutable spenders need preplanned incident response. When pausing and upgrade paths do not exist, the real safety margin is approval hygiene, wallet inventory, and rescue readiness.
No user funds lostcan still describe a severe custody failure. Primitive’s whitehat outcome is valuable precisely because it preserves the full exploit mechanics without the noise of a successful blackhat theft.- The clean design question is blunt: which exact pair and which exact option market are entitled to ask this connector to spend which exact wallet’s tokens right now, and what trusted state proves that before callback settlement begins?
Internal linkages
- Core recurring pattern: callback-origin-authentication-failure.
- Positive-design settlement companion: settlement-envelope-truth-security-object — the connector never preserved one authenticated settlement object binding the canonical pair, canonical option market, victim payer, and callback-stage meaning before approved tokens could be pulled.
- End-to-end route-design companion: route-commitment-across-callback-frame-and-settlement-envelope — Primitive’s connector never pinned one route-authenticated object spanning the flash-swap callback frame, canonical option market, and downstream payer / settlement meaning.
- Approval-funded route companion: approval-funded-route-authority-truth-security-object — the missing middle layer was a route-local authorization object that should have kept approved connector identity, canonical pair counterparty, victim payer, and option-settlement semantics fused before
uniswapV2Call()could exercise user allowance. - Adjacent pattern: account-confusion-root-of-trust-substitution — the callback bug was the decisive first breach, but the connector also trusted attacker-supplied option contracts as if they were legitimate market objects.
- Adjacent pattern: arbitrary-call-token-approval-drain — the money path still relied on dormant user approvals, but Primitive is more usefully remembered as a fake-settlement-context exploit than as a raw arbitrary-call router failure.
Control surface
-
The decisive leverage sat in Primitive’s connector callback authentication, the route / initiator binding around flash-swap settlement, and the connector’s authority as an already-approved spender for user wallets.
-
More specifically, the route never pinned one approval-funded authority object for
which pair may callback,which option market defines settlement,which wallet may pay, andwhat debt this callback is allowed to satisfy, so a fake pair plus fake option context could turn callback settlement itself into the allowance-consumption primitive. -
Primitive did not mainly fail because an option market was mispriced. It failed because the connector could be convinced it owed settlement in a fake market context, and user approvals turned that false obligation into real token movement.
-
Sources:
- https://primitivefinance.medium.com/whitehack-by-primitive-finance-most-funds-are-safe-user-action-required-4dd31c387b8
- https://primitivefinance.medium.com/postmortem-on-the-primitive-finance-whitehack-of-february-21st-2021-17446c0f3122
- https://github.com/OriginProtocol/security/blob/master/incidents/2021-02-22-PrimitiveFinance.md
- https://amberlabs.substack.com/p/exploiting-primitive-finances-approval
- https://etherscan.io/tx/0xa903a3b8e098e1e10f73070ef370b347950ef2a6c9037ce76e6a9944b8a05d39
- https://etherscan.io/tx/0x9c511b8063780104895c68d08e7dbb1f1557c59e3e96bd14ef190d9329e33113
- https://etherscan.io/tx/0xac9771a1dd347bf880b7db8c842ad2481cb69a7fae671e78267dc9e1e045d010
- https://contract-library.com/contracts/Ethereum/0x9daec8d56cdcbde72abe65f4a5daf8cc0a5bf2f9?tab=Source&line=1267
- https://contract-library.com/contracts/Ethereum/0x9daec8d56cdcbde72abe65f4a5daf8cc0a5bf2f9?tab=Source&line=1552
- https://contract-library.com/contracts/Ethereum/0x9daec8d56cdcbde72abe65f4a5daf8cc0a5bf2f9?tab=Source&line=2048
-
Last reviewed: 2026-08-04 UTC