Near Learn

Access keys as an attack surface

NEAR access key security: full-access vs function-call keys, why contract accounts with full-access keys can be rewritten, and how to audit stale keys.

Advanced5 min read3-question check

On NEAR an account is not one key — it is a list of access keys, and every key on that list can act for the account within its permissions. That list is part of your security model: for users, for your deployer account, and especially for the contract account itself.

CapabilityFull-access keyFunction-call key
Transfer NEARYesNo
Attach a deposit to a callYesNo — gas only, paid from its allowance
Call contract methodsAny contractOnly its receiver_id, optionally only listed method_names
Deploy / redeploy codeYesNo
Add or delete keys, delete the accountYesNo
What each key type can do

The bug#

Vulnerable — anyone can add a full-access key to the contract account
Rust
use near_sdk::{env, near, Promise, PublicKey};

#[near]
impl Vault {
    // "recovery" helper left in from development
    pub fn add_recovery_key(&mut self, public_key: PublicKey) -> Promise {
        // BUG: no authorization. The caller now controls vault.near
        Promise::new(env::current_account_id()).add_full_access_key(public_key)
    }
}

The fix#

Fixed — if the method must exist, only governance may call it, and never through a function-call key
Rust
use near_sdk::{assert_one_yocto, env, near, require, Promise, PublicKey};

#[near]
impl Vault {
    #[payable]
    pub fn add_recovery_key(&mut self, public_key: PublicKey) -> Promise {
        assert_one_yocto(); // a function-call key can't attach this deposit
        require!(
            env::predecessor_account_id() == self.governance, // e.g. a DAO account
            "only governance"
        );
        Promise::new(env::current_account_id()).add_full_access_key(public_key)
    }
}
Audit the key list — keep upgrade power with an owner or DAO, not a stray key
Shell
# list every key on the contract account (near-cli-rs)
near account list-keys vault.near network-config mainnet now

# any full-access key here can redeploy the contract.
# If the contract should be immutable, delete them all
# (a "locked" contract); if it should be upgradable,
# route upgrades through a DAO / multisig instead.

Check yourself

3 questions · progress saved in this browser

  1. 1.A DeFi contract’s code is audited and has no admin functions, but list-keys shows a full-access key on the contract account. What is the risk?
  2. 2.What can a function-call access key NOT do?
  3. 3.You want a contract to be provably immutable. What do you do?