Skip to main content
Sigvex

Timelock Operations

Detects timelock bypass vulnerabilities, missing delays for critical operations, and timestamp validation issues.

Timelock Operations

Overview

Remediation Guide: How to Fix Timelock Issues

The timelock operations detector identifies Solana programs where critical operations execute immediately without a required delay period, where timestamp validation is missing in time-dependent functions, and where timelock cancellation is not properly authorized. Timelocks give users time to react to pending changes (such as admin configuration updates or fund movements) and are a standard defense-in-depth mechanism.

Why This Is an Issue

Without timelocks, admin operations take effect immediately. This creates several risks:

  • No user response window (CWE-367): Users cannot withdraw funds or take protective action before a malicious configuration change takes effect.
  • Missing timestamp validation: Functions that depend on time but do not validate the clock can be exploited if the on-chain timestamp drifts or is manipulated within Solana’s tolerance window.
  • Unprotected cancellation (CWE-284): If anyone can cancel a queued timelock operation, an attacker can prevent legitimate pending actions from executing.

Timelocks are especially important for programs that manage significant value, as they provide a safety window between announcement and execution of changes.

How to Resolve

Native Solana

use solana_program::{
    account_info::AccountInfo, clock::Clock, program_error::ProgramError,
    sysvar::Sysvar,
};

const TIMELOCK_DELAY: i64 = 86400; // 24 hours

pub fn execute_queued_action(accounts: &[AccountInfo]) -> ProgramResult {
    let admin = &accounts[0];
    let proposal = &accounts[1];

    if !admin.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    let clock = Clock::get()?;
    let proposal_data = proposal.try_borrow_data()?;
    let queued_at = i64::from_le_bytes(proposal_data[0..8].try_into().unwrap());

    // Validate timestamp is reasonable
    if clock.unix_timestamp < queued_at {
        return Err(ProgramError::InvalidArgument);
    }

    // Enforce delay period
    if clock.unix_timestamp < queued_at + TIMELOCK_DELAY {
        return Err(ProgramError::InvalidArgument); // Too early
    }

    // Safe to execute
    Ok(())
}

Anchor

#[derive(Accounts)]
pub struct ExecuteProposal<'info> {
    pub admin: Signer<'info>,
    #[account(
        mut,
        has_one = admin @ ErrorCode::Unauthorized,
        constraint = proposal.execute_after <= Clock::get()?.unix_timestamp @ ErrorCode::TimelockNotExpired
    )]
    pub proposal: Account<'info, Proposal>,
}

#[account]
pub struct Proposal {
    pub admin: Pubkey,
    pub execute_after: i64,
    pub action: ProposalAction,
}

Examples

Vulnerable Code

pub fn update_admin(accounts: &[AccountInfo], new_admin: Pubkey) -> ProgramResult {
    let admin = &accounts[0];
    let config = &accounts[1];

    if !admin.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // VULNERABLE: immediate execution with no delay
    let mut data = config.try_borrow_mut_data()?;
    data[0..32].copy_from_slice(new_admin.as_ref());
    Ok(())
}

Fixed Code

pub fn propose_admin_change(
    accounts: &[AccountInfo],
    new_admin: Pubkey,
) -> ProgramResult {
    let admin = &accounts[0];
    let proposal = &accounts[1];
    let clock = Clock::get()?;

    if !admin.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Queue the change with a delay
    let mut data = proposal.try_borrow_mut_data()?;
    data[0..32].copy_from_slice(new_admin.as_ref());
    let execute_after = clock.unix_timestamp + TIMELOCK_DELAY;
    data[32..40].copy_from_slice(&execute_after.to_le_bytes());
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "timelock",
  "severity": "medium",
  "confidence": 0.50,
  "title": "Missing Timelock Delay for Critical Operation",
  "description": "Critical operation is executed immediately without a required delay. Admins should be required to queue operations and wait for a delay period before execution, allowing users time to respond.",
  "location": { "function": "execute_admin_action", "block": 0, "statement": 0 },
  "cwe": 367
}

Detection Methodology

The detector analyzes time-dependent patterns in the function’s intermediate representation:

  1. Timestamp check tracking: Identifies comparisons involving parameter-range variables or clock sysvar constants that indicate timestamp validation.
  2. Delay validation detection: Looks for binary comparisons with constants in the realistic delay range (60 seconds to 1 year) that represent timelock enforcement.
  3. Critical operation identification: Flags parameter-range variable assignments in functions containing “execute” or “admin” in the name as critical operations requiring delays.
  4. Cancellation analysis: Detects cancellation-like operations and checks whether admin authorization (CheckKey, CheckOwner) precedes them.
  5. Timestamp validation gaps: Functions with “timelock” or “delay” in the name that lack timestamp checks generate findings.
  6. Context adjustment: Confidence is reduced for Anchor programs and read-only functions.

Limitations

False positives:

  • Programs where the timelock is enforced in a separate instruction (propose/execute pattern) but the detector only sees the execute function.
  • Anchor programs where time constraints are enforced in account validation.

False negatives:

  • Timelocks implemented through CPI to external governance programs.
  • Custom clock implementations that do not use the standard sysvar.

References