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.
Recommended Fix
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
- Signer-gated init: require a trusted authority signer for initialization, preventing unauthorized front-runners.
- Two-phase init: first create the account with
SystemProgram::create_account(which is atomic), then initialize in the same transaction. - 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.