PDA Seed Validation
Detects incorrect PDA seed derivation patterns including wrong order, missing seeds, and non-deterministic sources.
PDA Seed Validation
Overview
Remediation Guide: How to Fix PDA Seed Validation
The PDA seed validation detector identifies incorrect seed derivation patterns in PDA operations. It checks for wrong seed order (discriminator should be first), missing required seed components, incorrect seed types, excessive seed counts, non-deterministic seed sources (timestamps, mutable data), and inconsistent seed patterns across multiple derivations in the same function.
Why This Is an Issue
PDAs are deterministically derived from seeds. Incorrect seed derivation leads to account confusion (accessing the wrong account), authorization bypasses (wrong authority checked), and state corruption (wrong state modified). These issues are among the most frequent audit findings in Solana programs. A misplaced seed or missing discriminator can silently cause the program to operate on an unintended account.
CWE mapping: CWE-20 (Improper Input Validation).
How to Resolve
Native Solana
// CORRECT: discriminator first, then entity identifiers
let (pda, bump) = Pubkey::find_program_address(
&[b"user-data", authority.key.as_ref(), mint.key.as_ref()],
program_id,
);
Anchor
#[account(seeds = [b"user-data", authority.key().as_ref(), mint.key().as_ref()], bump)]
pub user_data: Account<'info, UserData>,
Examples
Vulnerable Code
// Wrong seed order -- discriminator should be first
let (pda, _) = find_program_address(&[user.key.as_ref(), b"vault"], program_id);
// Missing discriminator -- just user key
let (pda2, _) = find_program_address(&[user.key.as_ref()], program_id);
// Non-deterministic seed -- timestamp changes
let ts = Clock::get()?.unix_timestamp;
let (pda3, _) = find_program_address(&[b"session", &ts.to_le_bytes()], program_id);
Fixed Code
// Correct: discriminator first
let (pda, _) = find_program_address(&[b"vault", user.key.as_ref()], program_id);
// Correct: includes discriminator
let (pda2, _) = find_program_address(&[b"user-account", user.key.as_ref()], program_id);
// Correct: deterministic seeds only
let (pda3, _) = find_program_address(&[b"session", user.key.as_ref()], program_id);
Sample Sigvex Output
{
"detector_id": "pda-seed-validation",
"severity": "medium",
"confidence": 0.70,
"description": "PDA derivation uses only one seed component. A discriminator should be included to distinguish PDA types.",
"location": { "function": "process", "block": 0, "stmt": 0 }
}
Detection Methodology
- PDA extraction: Identifies all PDA derivation operations (
find_program_address,create_program_address). - Seed decomposition: Breaks seed expressions into individual components and classifies their types.
- Order validation: Checks if a constant (discriminator) appears as the first seed component.
- Count validation: Flags single-seed derivations (missing discriminator) and excessive seeds (more than 4 components).
- Determinism check: Detects seeds from non-deterministic sources like clock syscalls.
- Pattern consistency: Compares multiple derivations in the same function for missing discriminators.
Limitations
- Seed analysis at the HIR level may not fully resolve complex expressions involving memory loads or function calls.
- The detector cannot determine the semantic meaning of seed components (e.g., whether a load is a string literal discriminator).
- Anchor programs with
seedsconstraints are flagged at reduced confidence.
Related Detectors
- PDA Path Confusion — detects missing program namespace in seeds
- Weak PDA Entropy — detects low-entropy seed sources