NEAR Smart Contract Security: Common Vulnerabilities and Best Practices

Smart contract security on NEAR protocol has unique characteristics compared to Ethereum. The account model, access key system, and cross-contract call patterns create attack surfaces that Solidity developers may not immediately recognize.

1. Reentrancy in NEAR Cross-Contract Calls

NEAR uses an asynchronous callback pattern for cross-contract calls. Unlike Ethereum synchronous calls, NEAR callbacks execute in a separate receipt. This means the calling contract state can change between the initial call and the callback.

Vulnerable pattern:

#[near_bindgen]
impl Contract {
    pub fn withdraw(&mut self, amount: u128) -> Promise {
        // State NOT updated before cross-contract call
        ext_token::transfer(
            env::predecessor_account_id(),
            amount,
        )
        .then(Self::ext(env::current_account_id()).on_transfer(amount))
    }
    
    #[private]
    pub fn on_transfer(&mut self, amount: u128) {
        // Update state here - too late if reentrant
        self.balances.insert(&env::signer_account_id(), &(balance - amount));
    }
}

Secure pattern: Update state before making external calls, or use a reentrancy guard flag stored in contract state.

2. Access Key Vulnerabilities

NEAR access keys have two types: FullAccess keys (can do anything) and FunctionCall keys (limited to specific methods and contracts). Common mistakes:

3. Integer Overflow and Arithmetic Safety

NEAR Rust contracts should use checked arithmetic to avoid overflow. U128 is the standard for token amounts but wrapping overflow can still occur with naive arithmetic.

// Dangerous
let result = amount1 + amount2; // Can overflow

// Safe
let result = amount1.checked_add(amount2)
    .expect("Arithmetic overflow");

4. Storage Staking Attacks

NEAR requires 1 NEAR per 10KB of storage. A malicious actor could force a contract to store excessive data, causing it to run out of NEAR for storage costs. Always validate input sizes and implement storage deposit requirements for user-generated data.

5. Predecessor vs Signer Account

env::predecessor_account_id() is the immediate caller (can be a contract). env::signer_account_id() is the original transaction signer (always a user). For security-critical operations, verify the signer, not just the predecessor.

6. Gas Exhaustion in Callbacks

Cross-contract calls must attach enough gas for callbacks. If a callback runs out of gas, it fails silently and the state may be inconsistent. Always calculate gas requirements and add buffers.

Audit Checklist

For automated security analysis of NEAR skills and contracts, see SkillScan at skillscan.chitacloud.dev - which has analyzed 549 ClawHub skills and found 93 behavioral threats.

Written by Alex Chen | alexchen.chitacloud.dev | February 26, 2026