Skip to main content
Sigvex

PDA User-Controlled Seeds Remediation

How to fix unvalidated user-controlled input in PDA seeds.

PDA User-Controlled Seeds Remediation

Overview

Related Detector: PDA User-Controlled Seeds

PDA user-controlled seed vulnerabilities allow attackers to derive arbitrary PDA addresses by supplying crafted seed data. The fix is to validate all user input before using it in PDA derivation, and to prefer signer public keys over raw instruction data as seed components.

Before (Vulnerable)

let user_data = &instruction_data[0..32];
let (pda, _) = find_program_address(&[user_data], program_id);

After (Fixed)

// Use validated signer key instead of raw instruction data
let user = &accounts[0];
if !user.is_signer {
    return Err(ProgramError::MissingRequiredSignature);
}
let (pda, _) = find_program_address(&[b"vault", user.key.as_ref()], program_id);

Alternative Mitigations

1. Input validation with bounds checking

let seed = &instruction_data[0..32];
// Validate seed length
if seed.len() > MAX_SEED_LEN {
    return Err(ProgramError::InvalidArgument);
}
// Add program-specific prefix
let (pda, _) = find_program_address(&[b"data", seed], program_id);

2. Allowlist for seed values

const VALID_TYPES: &[&[u8]] = &[b"vault", b"escrow", b"config"];

let seed_type = &instruction_data[0..6];
if !VALID_TYPES.iter().any(|t| *t == seed_type) {
    return Err(ProgramError::InvalidArgument);
}
let (pda, _) = find_program_address(&[seed_type, user.key.as_ref()], program_id);

3. Anchor declarative seeds

#[derive(Accounts)]
pub struct Process<'info> {
    #[account(seeds = [b"vault", user.key().as_ref()], bump)]
    pub vault: Account<'info, Vault>,
    pub user: Signer<'info>, // Must be signer -- prevents impersonation
}

Common Mistakes

Mistake 1: Using instruction data directly as the only seed

// WRONG: user controls entire PDA derivation
let (pda, _) = find_program_address(&[data], program_id);

Always include a static prefix and validated identity.

Mistake 2: Validating seed format but not authorization

// INSUFFICIENT: format is valid but user may access other users' accounts
if seed.len() == 32 { // Length check only
    let (pda, _) = find_program_address(&[seed], pid);
}

Also verify the seed corresponds to the signer’s identity.

Mistake 3: Using remaining_accounts as PDA seed source

// WRONG: remaining_accounts are attacker-controlled
let seed_account = ctx.remaining_accounts[0];
let (pda, _) = find_program_address(&[seed_account.key.as_ref()], pid);

Only use validated, typed accounts as seed sources.

References