CPI Authority Downgrade
Detects privileged accounts being passed to unvalidated programs via CPI, enabling authority hijacking.
CPI Authority Downgrade
Overview
Remediation Guide: How to Fix CPI Authority Downgrade
The CPI authority downgrade detector identifies Solana programs that validate an account’s authority (signer, owner, or key check) and then pass that validated account to an unvalidated external program via CPI. This creates a privilege escalation where the attacker controls the receiving program and gains access to the validated authority.
When invoke_signed is used, the attack is especially severe because the attacker’s program also receives PDA signing authority, enabling it to act as the program on behalf of its PDAs.
The detector tracks three escalating patterns:
- Validated signer to unvalidated program: Authority verified but passed to unknown program.
- Validated authority with
invoke_signed: PDA signing seeds delegated to unknown program. - Multi-authority delegation: Multiple validated accounts forwarded to a single unvalidated CPI target.
Why This Is an Issue
This vulnerability is a privilege escalation through trust delegation. The program correctly validates an account’s authority, which gives a false sense of security. However, passing that validated account to an unvalidated program effectively hands the authority to the attacker:
- The program verifies that account A is the authorized admin
- The program then calls an attacker-controlled program, passing account A
- The attacker’s program now has access to account A with its validated status
- If
invoke_signedis used, the attacker also gains PDA signing ability - The attacker can perform any operation that account A is authorized for
This pattern has been found in admin panels, token management, and upgrade authority flows.
CWE mapping: CWE-269 (Improper Privilege Management), CWE-732 (Incorrect Permission Assignment).
How to Resolve
// Before: Vulnerable -- validated authority passed to unvalidated program
pub fn admin_operation(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let admin = &accounts[0];
let external_program = &accounts[1];
// Admin is validated
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// VULNERABLE: external_program is not validated
let ix = Instruction {
program_id: *external_program.key,
accounts: vec![AccountMeta::new(*admin.key, true)],
data: data.to_vec(),
};
invoke(&ix, accounts)?; // Attacker's program receives admin
Ok(())
}
// After: Validate the target program before delegating authority
pub fn admin_operation(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let admin = &accounts[0];
let external_program = &accounts[1];
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// FIXED: validate the target program
if external_program.key != &TRUSTED_PROGRAM_ID {
return Err(ProgramError::IncorrectProgramId);
}
let ix = Instruction {
program_id: *external_program.key,
accounts: vec![AccountMeta::new(*admin.key, true)],
data: data.to_vec(),
};
invoke(&ix, accounts)?;
Ok(())
}
Examples
Vulnerable Code
pub fn delegate_mint_authority(accounts: &[AccountInfo]) -> ProgramResult {
let mint_authority = &accounts[0];
let mint = &accounts[1];
let target_program = &accounts[2];
// Authority is validated
if !mint_authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let mint_data = mint.try_borrow_data()?;
let mint_state = spl_token::state::Mint::unpack(&mint_data)?;
if mint_state.mint_authority.unwrap() != *mint_authority.key {
return Err(ProgramError::InvalidAccountData);
}
// CRITICAL: validated mint authority sent to unvalidated program
let ix = Instruction {
program_id: *target_program.key, // Attacker-controlled
accounts: vec![
AccountMeta::new(*mint_authority.key, true),
AccountMeta::new(*mint.key, false),
],
data: vec![],
};
invoke(&ix, accounts)?;
Ok(())
}
Fixed Code
const TRUSTED_DELEGATION_PROGRAM: Pubkey = pubkey!("TrUsTeD...");
pub fn delegate_mint_authority(accounts: &[AccountInfo]) -> ProgramResult {
let mint_authority = &accounts[0];
let mint = &accounts[1];
let target_program = &accounts[2];
if !mint_authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let mint_data = mint.try_borrow_data()?;
let mint_state = spl_token::state::Mint::unpack(&mint_data)?;
if mint_state.mint_authority.unwrap() != *mint_authority.key {
return Err(ProgramError::InvalidAccountData);
}
// FIXED: validate the target program
if target_program.key != &TRUSTED_DELEGATION_PROGRAM {
return Err(ProgramError::IncorrectProgramId);
}
let ix = Instruction {
program_id: *target_program.key,
accounts: vec![
AccountMeta::new(*mint_authority.key, true),
AccountMeta::new(*mint.key, false),
],
data: vec![],
};
invoke(&ix, accounts)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "cpi-authority-downgrade",
"severity": "critical",
"confidence": 0.85,
"description": "Validated signer account v0 is passed to an unvalidated program via invoke_signed at block 4. The target program has no CheckKey validation, allowing an attacker to receive both the validated authority and PDA signing capability.",
"location": { "function": "delegate_mint_authority", "offset": 7 }
}
Detection Methodology
The detector uses CFG-aware dataflow analysis:
- Dataflow analysis: Runs
DataflowAnalyzerto propagate validation state (signer checks, owner checks, key checks) across basic blocks, tracking variable aliases. - Validated account identification: Collects all accounts that have been validated via
CheckSigner,CheckOwner, orCheckKeyin dominating blocks. - CPI account extraction: For each
InvokeCpistatement, extracts the set of account variables passed to the CPI. - Program validation check: Determines whether the CPI target program has been validated via
CheckKey. If not, and if validated accounts are included in the CPI accounts, a finding is emitted. - Severity escalation: When
invoke_signed(seeds present) is used with unvalidated programs, severity is escalated because PDA signing authority is also delegated to the attacker.
Limitations
False positives:
- Programs that validate the CPI target in a separate function or instruction may be flagged.
- Well-known program IDs (System, SPL Token) used via constants that are not recognized as trusted.
False negatives:
- Authority validation through governance PDAs or multi-sig patterns may not be recognized.
- Indirect CPI through wrapper functions that obscure the authority delegation.
Related Detectors
- Arbitrary CPI — unvalidated CPI target program
- CPI Signer Simulation — false signer claims in CPI
- Missing Signer Check — missing signer validation