Solana Program Security:
What Actually Gets Exploited and Why

Author(s): Ilya Teterin, Pavel Morozov
Security researcher(s) at MixBytes
Introduction
What Actually Causes “Hacks” on Solana?
Breaking Down Concrete Exploits
ᅠ• Loopscale
ᅠ• Texture
ᅠ• GoonFi
What Problems Are Most Common in Solana Programs?
Token, Mint, Vault and Token-2022 Semantics
Account Graph Integrity
Envelope: Malformed Data, Panics and Runtime Envelope
PDA Namespace, Seeds and Initialization Races
CPI Trust Boundaries
Rent, Realloc, Close and Residual State
Account Ownership and Discriminators
Signer and Writable Flag Semantics
Conclusion
Introduction
Solana has quietly become one of the most active on-chain program ecosystems. Over the past few years it has grown from a high-throughput newcomer into a platform home to major DeFi protocols, consumer payment apps, trading infrastructure, and institutional-grade applications.
The network now ranks among the highest-throughput blockchains in production — the result of an architecture fundamentally different from EVM chains.

But scale brings exposure. Today billions of dollars in liquidity flow through the Solana ecosystem, making the protection of user and protocol funds a critical concern — and one that demands a security lens that goes beyond what EVM contract auditing alone can provide. Solana's unique execution model creates vulnerability classes that have no clean analogue on Ethereum, and general DeFi auditing experience alone is
not enough to catch them.

This article breaks down what actually causes incidents on Solana: real exploits, the architectural roots of the most common bugs, and a practical map of the vulnerability classes auditors encounter most often in production programs.
What Actually Causes “Hacks” on Solana?
As on Ethereum, not every incident comes down to a bug in on-chain logic. A large share of losses stems from compromised keys, infrastructure, or trusted off-chain services. Representative examples:

  • Drift Protocol (~$286M) — signature compromise without seed phrase theft
  • Step Finance (~$40M) — signature / treasury compromise
  • Upbit (~$30M) — centralized exchange; hot wallet compromised
  • SwissBorg (~$42M) — SwissBorg app; compromise of third-party staking API (Kiln)

At the same time, many exploits do trace to flaws in Solana programs. Examples:

  • Loopscale, credit/lending (~$6M) — spoofed price oracle / insufficient validation
  • Texture Finance (~$2M) — spoofed SPL destination / insufficient validation
  • GoonFi (~$254K) — bad trusted price feed accepted by the AMM / missing sanity bounds
Breaking Down Concrete Exploits
One Solana architectural trait quietly creates an entire class of mistakes. In EVM-style systems, if the contract already knows the correct address, passing that same address from the outside and then validating it would usually be redundant. The contract can just use the address it has in storage or hardcoded in its logic.

Solana works differently. A program does not go and fetch “its” accounts by itself. Every account the instruction will read from or write to is provided by the transaction builder. That is not an API convenience, but a core part of the execution model.

This easily collides with a normal developer instinct: “the user passed the account we need, so let’s use it.” But the attacker’s instinct is the opposite: “what if I pass a different account?” As a result, Solana programs must constantly prove that the accounts they received are exactly the accounts the protocol intended to use. Missing that proof is not an edge case, but one of the most common Solana-specific vulnerability classes.

Loopscale and Texture share insufficient user-supplied-data validation. GoonFi shows a neighboring failure mode: even when the account path is not obviously spoofed by the attacker, a program can still be unsafe if it accepts trusted pricing state without enough sanity checks. The mechanics and risk profiles differ.

A note on sources. Full on-chain program source is not publicly available for these projects, a common situation on Solana. The analysis below is based on on-chain transaction evidence, official post-mortems and team statements, and supporting artifacts such as client-side code recovered from prior web builds.
Loopscale
Loopscale lets users borrow against collateral. Collateral valuation relies on price oracles whose addresses—as is unavoidable on Solana—travel with the transaction’s account bundle. For most assets the program constrained which oracle addresses were permissible, but shortly before the incident support was added for RateX Principal Tokens, and validation on that code path was weaker than it needed to be.

An attacker supplied collateral worth less than one cent on the market, wired in a fake oracle that priced it as effectively unbounded, and could drain up to essentially all available liquidity—“borrowing” the full accessible stack against spoofed collateral.

At the product layer, Loopscale commonly assembled transaction legs on a backend that also gated oracle addresses. That is not a sufficient sole security control: an attacker can craft the malicious instruction without ever calling your API and submit it straight to the network.

Protocol rule of thumb: any input that materially changes instruction outcomes must be cryptographically anchored or enumerated on-chain. If you depend on a “trusted” off-chain assembly path anyway, on-chain you must still prove supplied values conform to what that path would have produced—via explicit allowlists, program IDs, PDAs, etc., depending on design. Loopscale lacked that binding; damage is on the order of ~$6M.
Texture
Here the vulnerability is even more blunt—and typically Solana-flavored. One code path took the recipient for LP mints from user-supplied accounts—expected under CPI/account-list mechanics—but failed to enforce it matched the protocol’s intended vault. Attackers routed LP issuance to arbitrary addresses instead of protocol-controlled escrow.

On Solidity it is common for the vault address to be hard-coded or derived so end users cannot substitute a rogue recipient wherever the developer already pinned the canonical target. On Solana the accounts still must ride with the transaction, yet the Texture program neglected to enforce “mint LP into our vault”—a ~$2M real-world reminder that account validation belongs on every mandatory dev/audit checklist.

Protocol rule of thumb: whenever an instruction mints, transfers, escrows, or credits value, the destination account is part of the security invariant. It is not enough that the account has the right mint or token-program shape; the program must prove that the destination is the intended vault, escrow, position, strategy, or user account. For protocol-controlled flows, that usually means binding the destination to stored config, PDA seeds, vault authority, mint, and market/strategy state—not merely accepting whichever token account the transaction provides.
GoonFi
GoonFi is a different flavor of incident. It does not look like a “classic” attacker-driven exploit where the winning transaction itself smuggles in a fake account and the program blindly trusts it. The affected WSOL/USDC market priced swaps from a GoonFi-adjacent external feed that carried two sides of the quote—bid-like and ask-like values.

Public sources do not explain where that feed’s off-chain input came from. On-chain, however, the sequence is clear enough: the feed was publishing normal SOL/USDC-like values around 82.7, then suddenly wrote a quote around 2.658, later degrading to near-zero and zero. That is not a market move; it is a pathological feed state. Searchers saw the opportunity and bought WSOL from GoonFi far below the broader market price, then unwound elsewhere.

The current on-chain evidence does not show that those searchers caused the bad price. The main beneficiaries look more like arbitrage / MEV accounts reacting to a publicly visible mispricing than like the source of the feed corruption. That distinction matters: the incident still drained a pool, but the failure mode appears to be bad trusted pricing accepted by the AMM, not malicious calldata alone.

Protocol rule of thumb: if a live market can be moved by a feed update, both the publisher path and the consuming program need sanity checks. A jump from ~82.7 to ~2.658 in the same second—more than 30x — should have tripped a circuit breaker, and active markets should reject near-zero or zero quotes unless those values have a very explicit, safe meaning.
What Problems Are Most Common in Solana Programs?
Public incidents are only part of the picture. Many critical bugs are caught in audits before they ever reach production, and looking at the problems that appear most often in public audit reports gives a more complete view of where Solana programs go wrong.

Vulnerabilities in Solana programs can be Solana-specific or generic. Generic issues are the kinds of bugs auditors also encounter on other chains, including EVM ecosystems. Here we focus on the Solana-specific part.

A Solana-specific bug almost never starts with a single bad arithmetic line. It usually starts with the wrong model of the account graph. A program receives a set of accounts: some sign the transaction, some are writable, some are PDAs, some belong to SPL Token or Token-2022, and some are forwarded into CPI. If the code validates only the local shape of an account, but not its role in the graph, authority, mint, owner, seeds, program id, lifecycle state, or dynamic position in remaining_accounts, the user can often supply an account set that is "almost right".

That is why the top Solana-specific classes that most often lead to High or Critical findings look like different forms of the same theme: the program trusts accounts more than it should.
Token, Mint, Vault and Token-2022 Semantics
This class covers cases where a protocol models SPL Token, Token-2022, mint/vault relationships, ATAs (Associated Token Accounts), wSOL/native SOL, transfer fees, delegates, freeze authority, or net-vs-gross accounting incorrectly.

A typical real-world shape is a rebalance or vault flow that checks the mint but not the token-account owner, authority, vault binding, or token program. The account looks right locally, yet value moves to the wrong role in the protocol.

Typical forms:

  1. An instruction updates internal accounting but does not perform the actual token transfer.
  2. A vault, mint, or collateral id is not bound to the expected strategy or market.
  3. wSOL/SOL conversion creates a DoS, rent, or close-account edge case.
  4. A standard Token-2022 transfer fee or a protocol-level host fee makes the gross amount differ from the net amount actually received.
  5. Incorrect mint validation allows creation of a pool, pair, or position with the wrong token.
  6. Fee owner, vault authority, or token account owner is checked in the wrong place or not checked completely.

How to check it:

  1. Build a token matrix for every instruction: mint, token account, vault, authority, token program, decimals, extension policy, and expected owner.
  2. Validate not only token_account.mint, but also the relationship vault -> mint -> strategy/market/config.
  3. For Token-2022, explicitly list supported and forbidden extensions.
  4. Test gross-vs-net transfer behavior: protocol fee, host fee, transfer fee, withheld amount, and extension-specific behavior.
  5. Test wSOL/native SOL paths separately: create ATA, sync native, close account, and rent receiver.
  6. For every transfer, compare before/after balances against internal accounting.
  7. For every fee account, validate mint, token program, owner, authority, and binding to the expected strategy, market, or config.
Account Graph Integrity
This class is an incorrect relationship between accounts. An account can have the right owner, discriminator, and layout, yet still be the wrong object: someone else's position, the wrong market, another config, an unrelated tick array, a fake user state, or the wrong obligation.

Typical forms:

  1. Source and target positions can be the same account.
  2. An account belongs to the correct program but is not tied to the required mint, config, or market.
  3. User state looks valid but does not belong to the user or strategy.
  4. A close/finalize instruction accepts an account with the right shape but from the wrong Node Consensus Network (NCN), epoch, or branch.
  5. Duplicate accounts let one logical object satisfy two roles that should be distinct.

How to check it:

  1. For every instruction, build an account matrix: role, owner, discriminator, seeds, signer/writable flags, and relationship checks.
  2. Validate all implicit relationships: position.market == market.key(), vault.mint == config.mint, user_state.owner == signer.
  3. Add negative tests for same-shape substitution: another market, another user, another vault, another config.
  4. Check source != destination when the operation must not be a self-operation.
  5. In Anchor, do not treat Account<T> as enough: has_one, constraint, seeds, and manual relationship checks must cover the business graph.
Envelope: Malformed Data, Panics and Runtime Envelope
Envelope is the class where business logic may be correct for normal input but break when the program receives malformed or unexpected account data. In DeFi program audits this usually means unchecked deserialization, missing length checks, wrong discriminators, stale zero-copy layout assumptions, unchecked indexing, unsafe slicing, or panic paths reachable through permissionless instructions.

Typical forms:

  1. unwrap() or an unsafe assumption leads to a panic.
  2. A parser expects a length or format but does not validate it before access.
  3. A zero-copy or manually deserialized account is accepted with the wrong size, version, or trailing bytes.
  4. DoS comes not from an economic attack, but from an exceptional execution path that any user can trigger.
  5. The code is stable on the happy path but lacks a negative corpus for hostile bytes.

How to check it:

  1. Search for unwrap, expect, unchecked indexing, slicing, unsafe, and manual deserialization.
  2. Before every account-data read, validate length, discriminator, owner, version, and trailing-bytes policy.
  3. Add malformed corpus tests: short data, long data, wrong discriminator, random bytes, duplicate sections, invalid TLV.
  4. For parser/runtime code, use fuzzing and differential tests.
  5. Check separately whether a panic can become a DoS for a permissionless instruction.
PDA Namespace, Seeds and Initialization Races
This class covers mistakes where deterministic addresses are treated as proof of the right logical object. PDA seeds define a namespace, invoke_signed can turn a PDA into program authority, and lifecycle rules decide whether the same address can be initialized, closed, or reused safely. If seeds are incomplete, the bump is non-canonical, the PDA is not bound to a parent entity, or multiple global config accounts can be created, the protocol may end up with a second "valid" object.

Typical forms:

  1. Multiple global configuration accounts are allowed.
  2. Missing PDA validation leads to repeated or unauthorized transfers.
  3. Derived address collisions or an overly broad namespace.
  4. A PDA can be closed or reinitialized by the wrong actor.
  5. Seeds/authority mismatch makes a position unmanageable or breaks withdrawals.

How to check it:

  1. Build a PDA registry: entity, seeds, bump source, initializer, close authority, and signer usage.
  2. Make sure seeds include all required domain separators: protocol, market, mint, user, config, chain id, nonce.
  3. For every PDA, add negative tests with a PDA derived for another user, market, or config.
  4. Check canonical bump usage and stored bump handling if the bump is reused later.
  5. Test pre-initialization, duplicate init, close/recreate, and init_if_needed paths.
CPI Trust Boundaries
The CPI class covers mistakes at the boundary between programs. A Solana CPI passes not only a call, but also a set of AccountInfos, writable/signers, program accounts, and sometimes signer seeds. If the caller trusts the target program or forwarded accounts without allowlists and post-checks, external program semantics become part of the attack surface.

A typical integration failure is a pricing, bridge, or routing path that accepts a same-interface external program or market account supplied by the caller. The call succeeds, but the trusted fact came from an untrusted program.

Typical forms:

  1. An attacker can substitute market, perp, or token program accounts and steal assets through CPI.
  2. Vault tokens are drained because the CPI authority/account set is wrong.
  3. Partial execution or an external call leaves the system in an intermediate state.
  4. Omnichain Application (OAPP) or executor account reassignment happens across a CPI boundary.
  5. Signer seeds or writable accounts are forwarded more broadly than the external call requires.

How to check it:

  1. Build a CPI map: target program, forwarded accounts, signer seeds, writable accounts, and expected post-conditions.
  2. Validate allowlisted program ids for token programs, DEXes, oracles, bridges, and external protocols.
  3. Reload/re-read accounts after CPI if the external program could have modified them.
  4. Minimize forwarded writable accounts and signers.
  5. Test malicious CPI targets and same-shape account substitution.
Rent, Realloc, Close and Residual State
This class covers Solana lifecycle behavior at the lamports and account-storage level: rent payer, close receiver, realloc size, stale bytes, forced ATA creation, close/recreate. These bugs often look like small lifecycle details, but in permissionless flows they can turn into rent extraction, DoS, or state confusion.

Typical forms:

  1. Forced ATA creation allows rent extraction.
  2. A close instruction does not validate that the account belongs to the required context.
  3. A wSOL close/sync path breaks order flow.
  4. Packed data length or realloc logic allows the wrong size.
  5. Custom program errors block cleanup/close flows.

How to check it:

  1. For every close path, state who can close, where lamports go, and which references are invalidated.
  2. Test close-then-recreate and recreate-with-same-address flows.
  3. Validate realloc behavior: new size, rent top-up/refund, and zeroing of stale bytes.
  4. For ATA creation, check who pays rent and whether an attacker can force someone else to pay.
  5. For wSOL/native SOL, validate sync/close/rent receiver policy.
Account Ownership and Discriminators
This class is a basic but critical check: an account must belong to the expected program and have the expected type/layout. On Solana this is not cosmetic. A foreign owner or wrong discriminator means the program is reading bytes created by another trust domain.

Typical forms:

  1. Oracle/scope program accounts are not validated.
  2. A stake pool accepts uninitialized or transient stake accounts.
  3. A Serum/DEX market or oracle account is not checked against the expected owner/type.
  4. A program reads state whose owner/discriminator does not match the expected program.

How to check it:

  1. For every account, explicitly validate owner, discriminator/type, data length, and executable flag where relevant.
  2. Do not trust an account only because it deserializes.
  3. For oracle, DEX, scope, or external program accounts, validate both program id and market/feed identity.
  4. Add tests with an account that has the right layout but the wrong owner.
  5. Check trailing bytes and versioning policy.
Signer and Writable Flag Semantics
This class covers mistakes in who actually signed an action and which account was allowed to change. On Solana, the signer flag lives on the account meta, while authority may be a stored key, PDA signer, multisig, delegate, or governance address. These layers are easy to confuse.

Typical forms:

  1. Missing multisig signer check.
  2. Multisig signers are not matched to instruction accounts.
  3. Executor/OAPP authority can be reassigned.
  4. Signature bypass in a Solana-specific verification flow.

How to check it:

  1. For every state mutation, write down the expected actor: user signer, admin signer, PDA signer, or multisig threshold.
  2. Check that the signer account matches the stored authority, not merely that it signed the transaction.
  3. For multisig, test missing signer, duplicate signer, wrong order, extra signer, and one signer below threshold.
  4. Check writable minimality: an unnecessary writable account can become attack surface.
  5. For PDA signers, validate seeds and the source of program-derived authority.
Conclusion
Auditing Solana programs has become an essential part of protecting user funds in the Solana ecosystem. The incidents and vulnerability patterns discussed above show that strong security work on Solana requires more than general DeFi experience alone. That background remains important, but it has to be combined with a focused understanding of Solana’s architecture and the ways its protocols are built and operated. For teams building on Solana, this makes specialized audit expertise a practical necessity rather than a nice-to-have.

At MixBytes, we bring deep, ecosystem-specific expertise to Solana audits. If your protocol is built on or migrating to Solana, reach out to us: https://mixbytes.io/contact
  • Who is MixBytes?
    MixBytes is a team of expert blockchain auditors and security researchers specializing in providing comprehensive smart contract audits and technical advisory services for EVM-compatible and Substrate-based projects. Join us on X to stay up-to-date with the latest industry trends and insights.
  • Disclaimer
    The information contained in this Website is for educational and informational purposes only and shall not be understood or construed as financial or investment advice.
Other posts