---
name: vulnhunter-solana
audience: specialist
description: Use when reviewing Solana program code for the account-model bug classes — unchecked account owner, absent signer, discriminator confusion, caller-controlled CPI seeds, PDA-derivation collision, SPL arithmetic overflow, close-and-reinitialize revival, and duplicate-account aliasing — the pattern census that stands in for absent scanners.
---

# vulnhunter-solana

Manual vulnerability pattern review for Solana Rust programs. Use this during
`solana-audit-flow`. There is **no Solana static-analysis step** — Slither has zero Solana
detectors (`slither --list-detectors` → 104, none for Solana), so any "slither Solana scan" is
a fabricated capability. Manual pattern matching is the primary coverage on Solana; the
automated Solana tooling is dynamic (anchor build warnings, litesvm/trident fuzzing), not
static taint analysis.

## High-priority vuln patterns

**1. Missing owner / program-id check (CRITICAL)**
Every account deserialized from instruction accounts must have its `owner` validated against the expected program ID. Pattern: `if account.owner != &expected_program_id { return Err(...) }`. Anchor's `#[account]` constraint handles this — but bare `AccountInfo` usage does not. Grep for `AccountInfo` without adjacent owner check.

**2. Missing signer check (HIGH)**
Privileged instructions must require `is_signer` on the authority account. Pattern: `if !authority.is_signer { return Err(ErrorCode::Unauthorized.into()) }`. Anchor's `Signer<'info>` type enforces this — but `AccountInfo` with manual check can omit it. Grep for privileged state mutations without `is_signer` guard.

**3. Account discriminator bypass (HIGH)**
Anchor programs use 8-byte discriminators at the start of account data to prevent type confusion. An attacker can pass a different account type that happens to deserialize without error if the discriminator is not validated. Verify `try_deserialize` is used (not `try_deserialize_unchecked`) for sensitive account types. Also check: can two different account types be interchanged via a crafted buffer?

**4. CPI signer / privilege escalation (CRITICAL)**
When making CPIs that require signing, the `invoke_signed` call must pass the correct `signer_seeds`. A bug where the caller-controlled account address matches a PDA derivation can allow privilege escalation. Pattern: verify `invoke_signed` uses program-controlled seeds, not caller-provided seeds, for authority PDAs.

**5. SPL token arithmetic overflow (HIGH in pre-checked code)**
SPL token math (amounts, decimals, fee calculation) must use `checked_*` arithmetic (`checked_add`, `checked_mul`, `checked_div`). Grep for arithmetic operators (`+`, `*`, `/`) directly on `u64` token amounts in pre-1.14 programs. Post-1.14, `overflow-checks = true` in `Cargo.toml` [profile.release] provides runtime protection; verify this is set.

**6. PDA derivation collision / seed manipulation (HIGH)**
Two different logical accounts can derive to the same PDA if seed inputs are insufficiently discriminated. Pattern: PDA seeds that include only user-controlled data (e.g., just a user public key) without a type discriminator can collide across account types. Verify seeds include a fixed type-specific prefix.

**7. Unchecked `AccountInfo.data` mutation (CRITICAL)**
Direct write to `account.data.borrow_mut()` bypasses Anchor's borsh serialization guarantees. Verify that data writes always go through `account.exit()` or Anchor's `Account::serialize` path.

**8. Rent-exempt check missing (LOW-MEDIUM)**
Accounts that fall below rent-exempt minimum can be garbage-collected by the runtime. Verify that account creation always includes the rent-exempt minimum lamport balance. Missing this is usually LOW severity unless it can be weaponized to force a denial-of-service.

## Order of ops

1. Grep the program source for `AccountInfo` usages. For each: verify owner check and signer check where required.
2. Grep for `invoke_signed` usages. For each: trace the `signer_seeds` derivation — are any seeds caller-controlled?
3. Grep for arithmetic operators on `u64`/`u128` token amounts. For each: confirm `checked_*` or `saturating_*` is used.
4. Review all `#[derive(Accounts)]` structs: for each `AccountInfo<'info>` field, check if an `Account<'info, T>` type with Anchor constraints would be more appropriate.
5. Review PDA seed construction: list all PDA seeds, confirm each includes a type prefix.

## When to pivot

- **Program is not Anchor:** apply the same patterns manually but via raw `solana_program` primitives. Owner checks are explicit `if account.owner != &program_id`. Signer checks are `if !account.is_signer`. There is no struct-level constraint validation.
- **Program is very large (> 5k LOC):** focus on CPI-adjacent code and privileged instruction handlers first; those have the highest attack surface density.

## Anti-patterns

- Do NOT report a "missing signer check" on accounts where the signer constraint is enforced at the Anchor struct level (`Signer<'info>`) and verified — only flag bare `AccountInfo` without manual check.
- Do NOT flag arithmetic as a finding if `overflow-checks = true` is confirmed in the release profile AND the code is post-1.14.
- Do NOT cite any Solana static-analysis detector (slither has none); if a step calls for one, it is stale — use manual review + dynamic fuzzing instead.

## Example

Grepping for missing owner checks in an Anchor program:

```bash
# Pattern 1: find bare AccountInfo usages
rg "AccountInfo<'info>" programs/ --type rust -n

# Pattern 3: find unchecked arithmetic on u64 token amounts
rg "[^a-z](\+|\*|/)[^=]" programs/ --type rust -n | grep -v "checked_"

# Pattern 4: find invoke_signed calls
rg "invoke_signed" programs/ --type rust -n
```

## Recording (chrono-vault)

The task packet's injected memory contract owns the exact call shape, sequence, and fields - see
`wirework-reflect`. Do not copy a `record(...)` example or add fields (including `source_task`) from
memory; the server binds them, and a baked example violates the run's authenticated schema. Memory is
best-effort telemetry and never gates the work. What is worth recording here is the task-specific
outcome: patterns checked, findings per pattern, AccountInfo/invoke_signed usage totals.

## Additional account / lifecycle / value classes
These extend the patterns above; the checklist duplicated the owner/signer/discriminator/CPI/PDA/arithmetic/rent classes already covered, so only the genuinely-additive classes are folded here.

- **Account substitution / missing relationship constraint (HIGH).** Beyond owner+discriminator, verify the *relationships* between accounts are enforced: `has_one`, `constraint = x.authority == user.key()`, "this vault belongs to this config", "the mint matches". Without them an attacker swaps in *their* vault / *their* token account while every individual account still passes its own owner check.
- **`close` / re-initialization revival (CRITICAL).** A closed account must have its lamports drained **and** its data zeroed / reassigned. Otherwise a closed account can be revived or re-initialized (reinit attack) into a privileged state.
- **Sysvar spoofing on native programs (MEDIUM–HIGH).** On non-Anchor programs, clock/rent and other sysvars passed as `AccountInfo` can be spoofed; verify the account key against the real sysvar id before trusting its data.
- **Duplicate mutable accounts / aliasing (HIGH).** The same account passed as two different `AccountInfo` params (aliasing) can defeat a balance/invariant check that assumes they are distinct. Reject or account for aliasing on mutable params.
- **Invariant preservation across EVERY path (CRITICAL for vaults/DEX/lending).** The 1:1 backing / collateralization / k-invariant must hold across fees, refunds, AND gas/rent reimbursement. Historical: gas-reimbursement drawn from the vault broke a bridge's 1:1 backing.
- **Swap direction / parity / slippage.** Can direction, price, or fee be manipulated (stale oracle, attacker-set rate, missing `min_out` slippage bound)?

Extra tooling beyond the flow's defaults: `cargo-geiger` (unsafe census), `cargo-fuzz` / trident (fuzz instruction handlers), `cargo-audit` (dependency CVEs). Related: [[known-advisory-backport-check]].
