Missing caller authorization in initialize_vault allows initialization frontrunning leading to complete vault takeover
initialize_vault does not validate the caller, allowing anyone to frontrun deployment and become the vault authority with full administrative control over user deposits.
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:
#[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 authoritypub system_program: Program<'info, System>,}
At line 24, the caller becomes the vault authority without any verification:
vault.authority = ctx.accounts.authority.key();
Vulnerable Scenario:
The following steps help understand the issue:
- Protocol team deploys the casino vault program to Solana mainnet.
- Attacker monitors blockchain for new program deployments or observes the deployment transaction in mempool.
- Attacker calls
initialize_vaultbefore the legitimate deployer, passing their own address as authority and an arbitrarywithdrawal_signer. - The vault PDA is initialized with the attacker as authority at line 24.
- Legitimate deployer's initialization transaction fails because the vault PDA already exists (Anchor's
initconstraint prevents re-initialization). - Attacker now has permanent control over vault operations including
emergency_pause,resume,set_authority, andset_withdrawal_signer.
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.
Recommendation
Add two new accounts to the InitializeVault struct with constraints that verify the caller is the program's upgrade authority:
#[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.
Resolution
Fair Casino: Fixed.
Zealynx: Verified.

