Cross-Account Relationship Validation
Detects missing validation of relationships between related accounts in CPI operations.
Cross-Account Relationship Validation
Overview
Remediation Guide: How to Fix Cross-Account Relationship
The cross-account relationship detector identifies operations where multiple accounts that should be related (e.g., token account to its mint, ATA to owner and mint) are used together without validating their relationship. Individual account validation (signer checks, owner checks) is insufficient when multiple accounts must form a valid relationship. An attacker can pass individually-valid accounts that do not actually relate to each other.
Why This Is an Issue
In Solana token operations, accounts are related: a token account belongs to a specific mint, an ATA is derived from an owner and mint, and authority accounts must match the token account’s owner. When these relationships are not validated, an attacker can supply a token account that belongs to a different mint or a different owner, enabling unauthorized token transfers, balance manipulation, or fund theft.
CWE mapping: CWE-863 (Incorrect Authorization).
How to Resolve
Native Solana
pub fn transfer(accounts: &[AccountInfo]) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
let authority = &accounts[2];
let mint = &accounts[3];
// Validate token account -> mint relationship
let source_data = spl_token::state::Account::unpack(&source.data.borrow())?;
if source_data.mint != *mint.key {
return Err(ProgramError::InvalidAccountData);
}
// Validate token account -> authority relationship
if source_data.owner != *authority.key {
return Err(ProgramError::InvalidAccountData);
}
invoke(&transfer_ix, accounts)?;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut, has_one = mint, has_one = authority)]
pub source: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub mint: Account<'info, Mint>,
}
Examples
Vulnerable Code
pub fn swap(accounts: &[AccountInfo]) -> ProgramResult {
let source_token = &accounts[0];
let dest_token = &accounts[1];
let authority = &accounts[2];
let token_program = &accounts[3];
// No relationship validation between accounts!
let ix = spl_token::instruction::transfer(
token_program.key, source_token.key, dest_token.key, authority.key, &[], amount
)?;
invoke(&ix, accounts)?;
Ok(())
}
Fixed Code
pub fn swap(accounts: &[AccountInfo]) -> ProgramResult {
let source_token = &accounts[0];
let authority = &accounts[2];
// Validate relationship
let source_data = spl_token::state::Account::unpack(&source_token.data.borrow())?;
if source_data.owner != *authority.key {
return Err(ProgramError::InvalidAccountData);
}
let ix = spl_token::instruction::transfer(/* ... */)?;
invoke(&ix, accounts)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "cross-account-relationship",
"severity": "high",
"confidence": 0.80,
"description": "Token program CPI uses accounts (v1, v2), (v1, v3), (v2, v3) without validating their relationships.",
"location": { "function": "swap", "block": 0, "stmt": 2 }
}
Detection Methodology
- Relationship tracking: Builds a tracker of validated account relationships from
CheckKey,CheckOwner, and comparison expressions. - CPI analysis: Extracts account variables from CPI calls and checks whether all pairwise relationships are validated.
- Token CPI focus: Applies higher-confidence checks to operations that appear to target the token program (heuristic detection).
- Lamport transfer checks: Also flags direct lamport transfers between accounts without relationship validation when both accounts appear in CPI contexts.
Limitations
- The detector cannot determine the actual token program instruction being called, so it applies heuristic checks to all multi-account CPIs.
- Relationship validation performed inside helper functions or via ATA derivation checks may not be visible.
- Anchor’s
has_oneconstraint is recognized at reduced confidence but not fully resolved.
Related Detectors
- CPI Account List Mismatch — validates CPI account list structure
- Missing Owner Check — detects missing account ownership validation