F-2026-0001·missing-validation

Missing caller authorization in initialize_vault allows initialization frontrunning leading to complete vault takeover

Fixedsolanavaulted25519
TL;DR

initialize_vault does not validate the caller, allowing anyone to frontrun deployment and become the vault authority with full administrative control over user deposits.

Severity
HIGH
Impact
HIGH
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
LOW
Exploitability
HIGH
02Section · Description

Description

The initialize_vault function does not validate the caller's identity, allowing anyone to call it and become the vault authority. Since Solana programs require manual initialization (unlike Ethereum's constructor pattern), the first caller to successfully execute initialize_vault gains complete control over the vault.

The vulnerable code at lines 268-292 shows no constraint on the authority account:

rust
#[derive(Accounts)]
pub struct InitializeVault<'info> {
#[account(
init,
payer = authority,
space = 8 + 32 + 32 + 32 + 1 + 1,
seeds = [VAULT_SEED],
bump
)]
pub vault: Account<'info, CasinoVault>,
#[account(mut)]
pub authority: Signer<'info>, // No constraint on who can be authority
pub system_program: Program<'info, System>,
}

At line 24, the caller becomes the vault authority without any verification:

rust
vault.authority = ctx.accounts.authority.key();

Vulnerable Scenario:

The following steps help understand the issue:

  1. Protocol team deploys the casino vault program to Solana mainnet.
  2. Attacker monitors blockchain for new program deployments or observes the deployment transaction in mempool.
  3. Attacker calls initialize_vault before the legitimate deployer, passing their own address as authority and an arbitrary withdrawal_signer.
  4. The vault PDA is initialized with the attacker as authority at line 24.
  5. Legitimate deployer's initialization transaction fails because the vault PDA already exists (Anchor's init constraint prevents re-initialization).
  6. Attacker now has permanent control over vault operations including emergency_pause, resume, set_authority, and set_withdrawal_signer.
03Section · Impact

Impact

Complete vault takeover is possible if an attacker successfully frontruns the initialization. The attacker gains full administrative control including the ability to pause withdrawals, rotate the withdrawal signer key, and transfer authority. All future user deposits would be at risk under attacker control with no recovery mechanism available.

04Section · Recommendation

Recommendation

Add two new accounts to the InitializeVault struct with constraints that verify the caller is the program's upgrade authority:

rust
#[derive(Accounts)]
pub struct InitializeVault<'info> {
// ... existing accounts ...
#[account(mut)]
pub authority: Signer<'info>,
// ADD THESE TWO ACCOUNTS:
/// Verify caller is the program's upgrade authority
#[account(
constraint = program_data.upgrade_authority_address == Some(authority.key())
@ ErrorCode::Unauthorized
)]
pub program_data: Account<'info, ProgramData>,
/// CHECK: Verify program data account matches this program
#[account(
constraint = program.programdata_address()? == Some(program_data.key())
)]
pub program: Program<'info, CasinoVault>,
pub system_program: Program<'info, System>,
}

The key additions are:

  • program_data account: Holds the program's upgrade authority and enforces that authority.key() matches the upgrade authority.
  • program account: Links the program data to the current program to prevent using another program's upgrade authority. This ensures only the account that deployed the program can initialize the vault, preventing initialization frontrunning attacks.
05Section · Resolution

Resolution

Fair Casino: Fixed.

Zealynx: Verified.

Status
Fixed
F-2026-0001

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx