Skip to main content
Sigvex

Generic Init Frontrun Remediation

How to fix initialization frontrunning vulnerabilities.

Generic Init Frontrun Remediation

Overview

Detector Reference: Generic Init Frontrun

This guide explains how to prevent initialization frontrunning where attackers pre-create accounts at expected addresses with malicious state.

Validate the account’s address matches the expected PDA before writing initialization data:

// Derive expected PDA
let (expected, bump) = Pubkey::find_program_address(
    &[b"config", authority.as_ref()],
    program_id,
);

// Validate address
require!(*account.key == expected, InvalidAddress);

// Validate empty
require!(account.data.borrow()[0..8] == [0u8; 8], AlreadyInitialized);

// Safe to initialize
let mut data = account.data.borrow_mut();
data[0..8].copy_from_slice(MY_DISCRIMINATOR);

For Anchor, use init with seeds:

#[account(init, payer = user, space = 8 + 64, seeds = [b"config", user.key().as_ref()], bump)]
pub config: Account<'info, Config>,

Alternative Mitigations

  1. Signer-gated init: require a trusted authority signer for initialization, preventing unauthorized front-runners.
  2. Two-phase init: first create the account with SystemProgram::create_account (which is atomic), then initialize in the same transaction.
  3. Check owner: verify the account is still owned by the system program before initialization to confirm no one else has claimed it.

Common Mistakes

  • Skipping PDA validation for non-PDA accounts: if accounts are not PDAs, any address can be pre-created. Validate the key or use PDAs.
  • Using init_if_needed: this skips initialization for pre-existing accounts, making front-running trivially exploitable.
  • Checking only discriminator: an attacker can set the discriminator to match your expected value. Validate the full address via PDA derivation.

References