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:
- Generating FunctionCall keys with deposit allowance too high for the use case
- Not rotating keys after suspected compromise
- Using FullAccess keys in automated systems (should use FunctionCall keys)
- Not validating that env::predecessor_account_id() matches expected caller in key-gated functions
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
- All state updates happen before cross-contract calls
- Access keys use minimum required permissions
- All arithmetic uses checked operations
- Storage deposits required for user data
- signer_account_id used for owner-only operations
- Gas budgets calculated with safety buffers
- All public functions have explicit access control
- Callback functions marked #[private]
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