Skip to main content
Sigvex

PDA Path Confusion

Detects PDA derivations without program-specific namespace seeds, risking cross-program collisions.

PDA Path Confusion

Overview

Remediation Guide: How to Fix PDA Path Confusion

The PDA path confusion detector identifies PDA derivations that use only user-controlled seeds (account keys, parameters) without a program-specific namespace discriminator. Without a namespace, different programs using similar seed patterns can derive the same PDA address, enabling cross-program account confusion attacks.

Why This Is an Issue

PDAs are derived from seeds and a program ID. If two programs both derive PDAs using only [user_key] as seeds, they produce different addresses because the program IDs differ. However, a malicious program can intentionally mimic another program’s seed pattern, and without namespace discriminators, the derivation logic becomes ambiguous and harder to audit. More critically, if programs share the same program ID across upgrades or forks, namespace-less PDAs can collide.

CWE mapping: CWE-345 (Insufficient Verification of Data Authenticity).

How to Resolve

Native Solana

// VULNERABLE: only user-controlled seeds
let (pda, _) = Pubkey::find_program_address(&[user.key.as_ref()], program_id);

// FIXED: add program-specific namespace
let (pda, _) = Pubkey::find_program_address(
    &[b"my-protocol-vault", user.key.as_ref()],
    program_id,
);

Anchor

#[account(seeds = [b"vault", user.key().as_ref()], bump)]
pub vault: Account<'info, Vault>,
// The string literal b"vault" acts as a namespace discriminator

Examples

Vulnerable Code

pub fn init(accounts: &[AccountInfo], program_id: &Pubkey) -> ProgramResult {
    // No namespace -- any program with same seed pattern could collide
    let (escrow, bump) = Pubkey::find_program_address(
        &[authority.key.as_ref(), mint.key.as_ref()],
        program_id,
    );
    Ok(())
}

Fixed Code

pub fn init(accounts: &[AccountInfo], program_id: &Pubkey) -> ProgramResult {
    // Namespace discriminator prevents cross-program collisions
    let (escrow, bump) = Pubkey::find_program_address(
        &[b"escrow-v1", authority.key.as_ref(), mint.key.as_ref()],
        program_id,
    );
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "pda-path-confusion",
  "severity": "medium",
  "confidence": 0.75,
  "description": "PDA is derived using only user-controlled seeds without a program-specific namespace discriminator.",
  "location": { "function": "init", "block": 0, "stmt": 0 }
}

Detection Methodology

  1. PDA syscall detection: Identifies find_program_address and create_program_address calls in assignments and invoke_signed seeds.
  2. Seed analysis: Classifies each seed component as namespace (constants, loads, program ID syscalls) or user-controlled (variables, parameters, account keys).
  3. Namespace check: Flags derivations that have user-controlled seeds but no namespace markers.
  4. Conservative classification: Constants and memory loads are treated as potential namespaces to avoid false positives.

Limitations

  • Constants used as seeds are treated as namespaces, but small numeric constants may not provide meaningful namespacing.
  • The detector cannot distinguish between a meaningful string discriminator and an arbitrary constant.
  • PDA derivations in Anchor programs with seeds constraints are recognized at reduced confidence.

References