Account Confusion / Root-of-Trust Substitution
- Name: account confusion / root-of-trust substitution
- Category: exploit pattern / root-object authenticity failure / user-supplied state validation failure / account-confusion
- Summary: Protocols sometimes accept a whole graph of caller-supplied accounts, contracts, token accounts, config objects, PDAs, or sysvars and then validate only the relationships among those inputs. If none of those checks proves that at least one root object is the canonical state object or integration endpoint the protocol actually meant to trust, an attacker can supply a self-consistent fake graph. The program then reasons correctly over attacker-authored fiction and turns counterfeit collateral, forged verification context, fake configuration, or fake integration endpoints into real mint, withdrawal, or authorization power.
- What it is:
- The program does not fail because every check is absent. It fails because the checks are only relative.
- Fields like
bank == collateral.bank,crate_token == crate_collateral_tokens.owner,account.owner == expected_program, orjar.token() == expectedUnderlyingcan all hold true inside an attacker-fabricated graph. - A user-supplied root account or contract becomes the hidden trust anchor for the rest of the validation path.
- If that root object is never proven canonical — for example by PDA derivation, hard-coded sysvar identity, immutable config lookup, or tightly scoped registry binding — then the whole graph can be counterfeit while still passing local consistency tests.
- The exploit monetizes whatever the graph controls: mint authority, bridge verification context, collateral admission, reward accounting, or market configuration.
- Typical preconditions:
- Users are allowed to supply multiple interdependent accounts or state objects to a security-critical instruction.
- The program treats one of those inputs as a root of trust for the rest of the validation path.
- Validation relies on pairwise equality checks, owner checks, discriminators, or shape checks more than canonical identity checks.
- The canonical account could have been derived or explicitly identified, but the code never enforces that derivation or identity.
- Downstream logic turns accepted state into something valuable: minting, borrowing power, signature verification, or redeemable withdrawals.
- Common vulnerable surfaces:
- Solana programs that accept market, bank, vault, reserve, or collateral accounts from the caller and only check their internal references
- sysvar or instruction-introspection flows where the program trusts a caller-supplied metadata account as if the runtime produced it
- cross-program integrations that accept Arrow / Saber / AMM / staking helper accounts from the user without pinning a canonical root
- delegated-wallet or module systems that read
delegate, relayer, signer, or source identity from caller-controlled payload fields and then check permissions against that named identity instead of the real caller - EVM controllers, routers, vault migrators, or strategy managers that accept arbitrary pool / jar / vault / helper addresses from the caller and only validate interface shape or local pointer consistency
- account graphs where the program verifies
ownerandmintbut never proves that the enclosing config account is the intended one - Anchor or custom-framework code paths that use
UncheckedAccountor manual validation for one decisive root account
- Exploit sequence:
- The attacker identifies an instruction or call path whose validation treats one caller-supplied account or contract as the starting truth for other objects.
- They create a fake root account, fake runtime-metadata account, or fake integration contract under their control.
- They create the dependent accounts or helper contracts needed to make every local equality, owner, mint, or pointer relation appear valid.
- They submit the counterfeit graph to the target instruction or controller path.
- The protocol accepts the graph because every checked relationship holds inside the fake state universe.
- The attacker converts that accepted fiction into real value: minted tokens, forged bridge messages, released collateral, or borrowable solvency.
- Variants:
- Fake root configuration account — the protocol trusts an attacker-created bank, market, vault, or config account and then accepts every dependent account that points back to it, as in Cashio.
- Fake sysvar / introspection account — the program accepts attacker-authored runtime metadata and treats it as proof that a verification step already happened, as in Wormhole.
- Fake integration endpoint — the protocol accepts an attacker-supplied pool, jar, vault, or helper contract that satisfies the expected interface but is not one of the canonical integration targets the protocol meant to trust, as in Pickle.
- Cross-market role substitution — the protocol accepts a real derivative, receipt token, or market-local endpoint from market A as if it were the canonical reserve, repay, collateral, or payout object for market B. The root object is not fake in the narrow
deployed by attacker and meaninglesssense; it is counterfeit because its issuance lineage and market-local role are wrong for the flow now trusting it, as in Cork. - Forged principal field — the protocol reads
delegate, signer, relayer, or source identity from payload data, proves that named identity is authorized somewhere else, and never proves the real caller owns that identity, as in New Market Trading. - Self-consistent fake collateral graph — the attacker manufactures a complete dependency chain around a worthless asset so the protocol accepts it as authentic collateral.
- Owner-only authenticity failure — the program checks that accounts are owned by the expected program or have the expected discriminator, but never proves that the specific account identity is canonical.
- Detection heuristics:
- Ask which input account is the first trusted fact in the validation path. If every answer is another caller-supplied account, the root of trust may be missing.
- Treat
has_one, equality, and pointer checks among user-supplied accounts as consistency checks, not authenticity proofs. - Re-run the exploit mentally with the attacker creating all checked accounts together. If the checks still pass, the validation is probably circular.
- Review every
UncheckedAccount, sysvar-like input, and manual validation block as a likely root-of-trust boundary. - Verify whether the program recomputes expected PDAs, checks canonical sysvar ids, or binds config accounts to immutable deployment-time registries.
- Ask whether a real token or endpoint from one market can satisfy a higher-trust role in another market purely because it shares interface shape, expiry metadata, or local pointer consistency. If yes, the counterfeit root may be a wrong-lineage object rather than an obviously fake one.
- Defenses:
- Bind at least one root account in every security-critical graph to canonical identity: PDA seeds, hard-coded sysvar ids, immutable registries, or tightly controlled config state.
- Recompute expected PDA addresses inside the program and compare them explicitly instead of trusting supplied account identity.
- Verify not just
owneranddiscriminator, but also whether the account key is the specific key the instruction meant to trust. - Avoid taking security-critical sysvars or configuration roots as arbitrary user inputs when the runtime or deployment config can supply them deterministically.
- Bind market-local roles and issuance lineage together. A transferable derivative, receipt token, or helper-owned endpoint from market A should not become reserve, repay, collateral, or payout truth for market B unless a separate explicit admission rule says that cross-market reuse is allowed.
- Minimize
UncheckedAccountuse on trust-anchor accounts; if manual validation is required, make the canonical-identity check the first gate. - Treat account-graph validation as a provenance problem, not just a typing problem.
What sound design looks like
- A healthy validation graph starts from at least one fact the caller cannot manufacture locally. The program should derive, load, or pin the canonical market root, verifier account, sysvar, config PDA, helper endpoint, module delegate, or registry-approved integration target before it starts checking the rest of the supplied graph. The first question should be
is this the exact object we meant to trust?, notdoes this object point consistently to the other objects the caller also supplied? - Relative checks should hang off a proven root instead of trying to become the proof.
has_one, owner, mint, discriminator, interface-shape, and pointer-equality assertions are still useful, but only after the protocol has already anchored the graph to canonical identity. Otherwise the attacker is free to author both sides of every relation and hand the program a beautifully consistent lie. - Root identity should usually be recomputed at the point of use rather than inherited from the user. In Solana-style account graphs that means PDA derivation and canonical sysvar ids; in EVM integrations it means immutable config, governance-controlled allowlists, registry lookups, route hashes, or other deployment-pinned endpoint identity. The broader positive-design habit is to keep
which object is allowed to define reality here?fused to one protocol-owned answer before any balances, signatures, rewards, or collateral logic consumes it. - Downstream modules should receive scoped trust, not generic
this looked right upstreamtrust. A healthy design keeps the set of objects that can certify collateral, signature validity, reward entitlement, hook identity, or withdrawal authority deliberately small and domain-specific, so one counterfeit market / bank / jar / helper cannot silently become a universal truth source for the rest of the flow. - Interface compatibility should only ever be a secondary admission test. A contract that implements the right functions, an account that has the right owner, or a sysvar-shaped blob with the right layout is not yet legitimate. Sound design still asks where that object’s authority came from and whether the protocol itself can independently name the one instance that was supposed to matter.
- Market-local role truth should stay fused to issuance lineage. A derivative token, receipt token, or helper endpoint that is perfectly real inside market A is still counterfeit if the protocol later lets it masquerade as reserve, repay, collateral, or payout truth for market B without an explicit protocol-owned admission rule.
- The clean design question is: if this instruction mints, withdraws, spends approval, or authenticates a message, where exactly did the system prove the decisive root object was canonical before any caller-supplied graph got to elaborate on top of it?
Three root-authenticity questions reviewers should not collapse
- Which exact object is canonical? A market, bank, sysvar, helper, delegate, module, or verifier can be real, interface-compatible, and internally consistent while still being the wrong root object for this flow.
- What scoped authority does that canonical object actually certify? A legitimate endpoint might only certify local debt, local collateral, one market-local role, one signer relation, or one callback context. That is not automatically authority to define payer identity, cross-market reserve truth, or the whole helper-owned
read -> approve -> repay -> mint -> transferchain. - When may downstream code spend against that certification? Even after the protocol names the right root object, helpers still need to prove the certified fact has been consumed or realized before leftovers, minted claims, delegated transfers, or borrower-facing callbacks become payout authority.
- The useful split is therefore not just
fake versus real object; it iswrong root object,right root but wrong authority class, andright authority class but downstream code spent against it too broadly or too early. - Cashio mostly fails at the first question, Cork lives on the first and second together, and Onyx is the sharp helper-side reminder that once a counterfeit market is accepted, every later step in the wrapper-owned authority chain becomes attacker-authored fiction too.
Failure-prevention checklist
- For every security-critical instruction, write down one non-user-authorable root fact that starts the trust chain: canonical PDA, canonical sysvar id, immutable config address, registry-approved endpoint, or deployment-pinned contract.
- Recompute root-object identity inside the program. Do not accept
the caller supplied the right-looking accountwhen the protocol can derive or load the exact account it meant to trust. - Treat all pairwise graph checks (
has_one, pointer equality,owner,mint, discriminator, interface support) as secondary checks that only matter after a canonical root has been established. - Make root-object validation the first gate in the instruction, before balance reads, callback setup, reward math, or mint / withdrawal effects.
- Separate
correct program owns this accountfromthis is the exact account this flow intended to trust. Ownership and type checks are necessary but almost never sufficient. - Minimize or eliminate attacker choice over runtime metadata objects such as sysvars, market roots, bank configs, verifier accounts, helper contracts, staking endpoints, and module delegates.
- If a user must supply an integration endpoint, bind it to an allowlist, immutable registry, or precommitted route hash rather than validating only its interface shape.
- When a helper composes several privileged steps, write down the whole authority chain it is trusting — for example
borrowBalanceCurrent -> underlying -> repay sink -> remint target -> final transfer— and prove each step is still anchored to the same protocol-owned root object. - For market-local derivatives, receipt tokens, or helper-owned endpoints, prove issuance lineage and role admissibility explicitly before letting them cross into another market’s reserve, repay, collateral, or payout path.
- Make fake-graph testing part of review: can an attacker create every checked account fresh and still satisfy validation? If yes, the trust root is probably missing.
- Add negative tests for counterfeit roots specifically: fake bank + fake collateral graph, fake sysvar, fake market, fake jar, fake delegate, fake staking contract, fake module, fake hook context.
- Keep downstream blast radius bounded. Even if a counterfeit graph slips through, caps, per-market limits, and delayed withdrawals can stop
authenticated fictionfrom becoming instant full-value loss.
Audit prompts
- What exact object is the first trusted fact in this instruction or helper path, and how is its identity proven independently of user-controlled inputs?
- If the attacker created every checked account or contract in this flow from scratch, which single check would still fail and why?
- Does the code prove canonical identity, or only local consistency among the supplied objects?
- After naming the canonical root object, what exact authority class does it certify: one local market role, one payer identity, one reward source, one verifier context, or the whole downstream helper chain?
- If a helper performs several privileged steps, which exact object defines each step in that chain: debt read, token identity, repay sink, remint target, delegated caller, final recipient, or callback surface?
- Can a caller swap in a fake sysvar, fake config root, fake market, fake vault, fake staking contract, fake module, or fake delegate while preserving all
owner,mint, and pointer checks? - Are any decisive trust-anchor accounts typed as
UncheckedAccount, rawAccountInfo, arbitrary address parameters, or interface-only contracts with manual validation later in the flow? - Is there any place where
owned by the expected programorimplements the expected interfaceis being mistaken foris the canonical endpoint we intended to trust? - Does the protocol derive expected PDAs and associated accounts itself, or does it let the caller choose them and merely verify relationships afterward?
- If the flow integrates external helpers, what exactly binds those helpers to an immutable registry, deployment-time config, or governance-controlled allowlist?
- Could a real derivative or endpoint from market A be accepted as a canonical reserve, repay, collateral, or payout object for market B without crossing an explicit admission boundary?
- Could a counterfeit root object still monetize without reentrancy or a second bug? If yes, this pattern is likely primary even if callbacks appear later.
- If the counterfeit graph were accepted once, what exactly limits damage before minted value, collateral power, or delegated authority can be cashed out elsewhere?
Lessons that should already be standard
- Consistency is not authenticity. A graph of attacker-authored objects can agree with itself perfectly and still be worthless.
- Security-critical validation needs one fact the attacker cannot manufacture locally. If every important fact comes from the same caller-supplied universe, the protocol is authenticating fiction.
Owned by program Xis not the same asthe specific account protocol X meant to trust. Type safety and provenance are different properties.- Sysvars, config roots, helper contracts, and module delegates are all trust anchors when downstream logic turns them into mint, withdrawal, or authorization power.
- Interface compatibility is a dangerous proxy for legitimacy. Many counterfeit-endpoint exploits succeed precisely because the fake contract behaves enough like the real thing to pass shape checks while changing who defines truth.
Canonical endpointis still not the whole question. Review also needs to preserve what authority that endpoint actually certifies and which later helper steps are still allowed to spend against that certification.Real tokenis not enough either. A genuine derivative or receipt token from one market can still be a counterfeit trust root in another if the protocol never proves the market-local role and issuance lineage match the flow now consuming it.- In account-oriented runtimes especially, provenance checks often matter more than arithmetic checks. The catastrophic bug is frequently
wrong object authenticated, notmath wrong once object was chosen. - Reviewers should ask
what root object did we mean to trust?before asking whether the rest of the account graph lines up. That question catches classes of bugs that dozens of relational assertions can miss. - Counterfeit-root exploits deserve treasury-grade blast-radius planning. Once fake state is blessed as canonical, every downstream invariant built on that state becomes an extraction path.
Where this pattern stops
- Stay here when the decisive failure is that the protocol accepted the wrong root object — market, bank, jar, verifier account, staking contract, or helper endpoint — as the thing allowed to define reality for the rest of the flow.
- Use a quick classifier when a callback also appears later in the exploit path:
- If accepting the counterfeit root object already let the attacker fabricate balances, rewards, collateral, signatures, or settlement authority, this note is usually the primary home.
- If the counterfeit endpoint only became dangerous because it provided a callback window into stale protocol state, move first to reentrancy.
- A simple test is
could this fake root still have paid without re-entry?If yes, stay here. Convergence and Cashio are clean yes-cases; Exactly is the mixed case.
- Move first to reentrancy when the trusted object itself was canonical and the sharper question is simply that external control returned before accounting or authorization state was finalized.
- Exactly Protocol sits on the seam: the fake market was first the counterfeit trust anchor and only then the callback trampoline. Use this note when you care about how the helper authenticated the wrong endpoint; use the reentrancy note when you care about what the hostile callback did with that misplaced authority.
- Move to approval-by-address-mutable-code-execution when the protocol originally approved the right address but execution later resolved to different code because the implementation behind that address changed.
- Move to arbitrary-call-token-approval-drain when the helper or router never needed to masquerade as a canonical root object at all; it already exposed attacker-directed calls over existing approvals.
- High-signal incidents:
- [[../../../incidents/approvals-wallets-integrations/cashio-brrr-fake-bank-account-confusion-infinite-mint-2022-03-23|Cashio
brrrfake-bank account-confusion infinite mint (2022-03-23)]] — Cashio validated a large collateral-account graph but never proved that the suppliedbankaccount was the canonical one, so the attacker built a fake Arrow / Saber / collateral universe and minted2B CASHagainst worthless inputs. - forged VAA mint (2022-02-02) — Wormhole’s Solana verifier accepted a fake instructions sysvar account, turning attacker-authored metadata into forged guardian-signature success and then into
120,000unbacked wrapped ETH. - fake-jar controller code-injection drain (2020-11-21) — Pickle’s controller treated attacker-supplied contracts as legitimate jars because they implemented the expected interface, then routed privileged controller-context logic and approvals through those fake endpoints until
19.76M DAIwas drained. - Exactly Protocol DebtManager malicious-market permit account-rebinding reentrancy drain (2023-08-18) — Exactly’s periphery helper let a caller-supplied fake market become both the permit-verification anchor and the next privileged execution target, so account context rebound to a victim and a re-entrant deleverage path drained real positions.
- [[../../../incidents/approvals-wallets-integrations/convergence-finance-cvxrewarddistributor-claimmultiplestaking-fake-staking-contract-reward-mint-2024-08-01|Convergence Finance CvxRewardDistributor
claimMultipleStaking()fake-staking-contract reward mint (2024-08-01)]] — Convergence let callers choose the contract that certified staking rewards, so a malicious interface-compatible endpoint returned fabricatedCVGclaims and turned the reward distributor into a58M CVGmint path. - [[../../../incidents/approvals-wallets-integrations/visor-finance-vvisr-staking-deposit-owner-hook-fake-from-unbacked-share-mint-drain-2021-12-21|Visor Finance vVISR staking
deposit()owner()hook / fake-fromunbacked-share mint drain (2021-12-21)]] — Visor let a caller-suppliedfromcontract self-certify as the authorized payer and then author the delegated-transfer code path itself, so counterfeitvVISRstill redeemed for realVISRreserves. - NFTLiquidation fake-oToken substitution self-liquidation drain (2024-09-26) — Onyx’s liquidation helper accepted caller-supplied market contracts as if they were canonical repay / collateral markets, then let that counterfeit root object define
borrowBalanceCurrent(),underlying(),mint()success, and even the final token-return callback before the helper proved the real borrow was cleared. - [[../../../incidents/approvals-wallets-integrations/cork-protocol-weeth-ds-as-ra-unauthenticated-corkhook-beforeswap-cross-market-redemption-drain-2025-05-28|Cork Protocol weETH-DS-as-RA + unauthenticated CorkHook
beforeSwap()cross-market redemption drain (2025-05-28)]] — Cork let a new market treat a legitimate market’sweETH-DStoken asRA, then trusted attacker-shaped Uniswap v4 hook context strongly enough to mint claims against that counterfeit role assignment and cash them out through the original market’s real redemption path. - [[../../../incidents/approvals-wallets-integrations/new-market-trading-squidroutermodule-caller-supplied-delegate-permission-spoof-safe-module-drain-2026-05-25|New Market Trading SquidRouterModule caller-supplied
delegatepermission-spoof Safe-module drain (2026-05-25)]] — New Market Trading’s third-party Safe module checked a real delegate copied from its own permissions registry but never proved the caller was that delegate, so forged approval-and-swap bundles drained 88 client Safes through attacker-owned pools.
- [[../../../incidents/approvals-wallets-integrations/cashio-brrr-fake-bank-account-confusion-infinite-mint-2022-03-23|Cashio
Internal linkages
- Broad root-authenticity companion: root-object-authenticity-and-trust-anchor-recomputation-discipline — use this when the sharper design question is what exact canonical root object the protocol should have recomputed or pinned before any caller-supplied graph was allowed to define reality at all.
- Payment-boundary companion: canonical-payer-identity-and-receipt-binding-discipline — useful when the counterfeit root object was specifically allowed to define who the payer was, which rail counted as payment, or whether a mint path could proceed without protocol-owned receipt proof, as in Visor.
- Positive-design companion set: reserve-backed-claim-truth-security-object, approval-funded-route-authority-truth-security-object, market-role-authenticity-and-cross-market-token-segregation, execution-envelope-identity-pinning, and bridge-truth-security-object — use the domain-specific companion that matches the thing the counterfeit root was allowed to define: reserve backing, market-local role authenticity and cross-market segregation, spender / route authority, helper / envelope identity, or bridge message truth.
- Adjacent pattern: bridge-verification-failure — use that note when the preserved lesson is destination-side message verification failure; use this note when the decisive bug is that the verifier trusted the wrong root account in the first place.
- Adjacent pattern: reentrancy — use that note when the trusted object was real but the protocol yielded control before its own state became true; use this note when the exploit first needed a counterfeit root object to make the callback or privilege handoff meaningful.
- Adjacent pattern: counterfeit-collateral-admission — both families turn fake assets into solvency, but this note is about runtime account-authenticity collapse rather than offchain asset-listing or issuer-verification failure.
- Adjacent pattern: message-source-authentication-failure — both concern provenance, but this note focuses on user-supplied state graphs and canonical-root mistakes rather than bridge-emitter identity.
- Adjacent pattern: approval-by-address-mutable-code-execution — both are about authenticating the wrong object, but that note is about code identity drifting after approval whereas this note is about callers supplying a counterfeit root object in the first place.
- Adjacent pattern: arbitrary-call-token-approval-drain — both can monetize standing approvals, but this note is the sharper home when the approved object only became dangerous because the protocol first treated a counterfeit endpoint as canonical.
- Best current exemplars: cashio-brrr-fake-bank-account-confusion-infinite-mint-2022-03-23, wormhole-guardian-signature-verification-bypass-2022-02-02, pickle-finance-pdai-evil-jar-fake-jar-controller-code-injection-drain-2020-11-21, exactly-protocol-debtmanager-malicious-market-permit-account-rebinding-reentrancy-drain-2023-08-18, convergence-finance-cvxrewarddistributor-claimmultiplestaking-fake-staking-contract-reward-mint-2024-08-01, visor-finance-vvisr-staking-deposit-owner-hook-fake-from-unbacked-share-mint-drain-2021-12-21, onyx-protocol-oeth-nftliquidation-fake-otoken-substitution-self-liquidation-drain-2024-09-26, cork-protocol-weeth-ds-as-ra-unauthenticated-corkhook-beforeswap-cross-market-redemption-drain-2025-05-28, and new-market-trading-squidroutermodule-caller-supplied-delegate-permission-spoof-safe-module-drain-2026-05-25.
Control surface
-
The leverage sits wherever the protocol decides which account, contract, or object is allowed to define reality for the rest of a validation graph.
-
The easy mistake is to think
all the references line upmeansthe state is authentic. It only means the attacker was internally consistent. -
A secure validation graph needs one or more facts that the attacker cannot author: canonical PDA derivation, canonical sysvar identity, immutable deployment config, registry-bound contract identity, or another trust anchor outside the user-supplied graph.
-
Sources:
- https://www.sec3.dev/blog/cashioapp-attack-whats-the-vulnerability-and-how-x-ray-detects-it
- https://www.certik.com/blog/cashio-app-incident-analysis
- https://github.com/cashioapp/cashio/commit/7df658184c2610139fa2c0058363c66b28add4c4
- https://wormholecrypto.medium.com/wormhole-incident-report-02-02-22-ad9b8f21eec6
- https://kudelskisecurity.com/research/quick-analysis-of-the-wormhole-attack
- https://www.certik.com/blog/wormhole-bridge-exploit-incident-analysis
- https://dedaub.com/blog/the-11m-cork-protocol-hack-a-critical-lesson-in-uniswap-v4-hook-security/
- https://github.com/banteg/evil-jar
- https://github.com/pickle-finance/protocol/blob/4d7ecfa766536622848a29407c0464283d11fb67/src/controller-v4.sol#L250-L370
-
Last reviewed: 2026-07-26 UTC