- Category: upgrades
- Severity: Medium
- Rule name:
upgrade_risk
S010 analyzes upgrade, admin, and initialization mechanisms. It walks impl blocks and reports:
- Governance — an upgrade/admin function (e.g.
upgrade,set_admin, anything matching the upgrade/admin heuristic) that mutates state withoutrequire_auth. - InitPattern — an initialization function with no re-init guard, so it can be called more than once.
- Timelock — upgrade functions are checked for a delay/timelock reference so you can confirm the delay is actually enforced.
Upgrade and admin paths are the keys to the kingdom. An unauthenticated upgrade lets anyone swap the contract's WASM; an initialize callable twice lets an attacker re-seize ownership; an instant, single-key upgrade with no timelock gives one compromised key total control. These are the single-key takeover paths that turn a small key leak into a full contract compromise.
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env};
#[contracttype]
pub enum DataKey {
Admin,
}
#[contract]
pub struct Upgradeable;
#[contractimpl]
impl Upgradeable {
// S010: upgrade path mutates state with no require_auth and no timelock.
pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
env.deployer().update_current_contract_wasm(new_wasm_hash);
}
}#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env};
#[contracttype]
pub enum DataKey {
Admin,
}
#[contract]
pub struct Upgradeable;
#[contractimpl]
impl Upgradeable {
pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
// Require the admin (ideally a multisig/timelock account) to authorize.
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();
env.deployer().update_current_contract_wasm(new_wasm_hash);
}
}- Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Base score: 9.8
- Rating: Critical
An unauthenticated upgrade path is a full takeover (Critical). The catalog assigns the category a Medium default because many S010 findings are governance-hardening recommendations (missing timelock, single-key admin) rather than open auth gaps; rate each finding by whether an auth guard is actually missing.
- Add
require_auth(orrequire_auth_for_args) on every upgrade and admin path, authorizing the admin principal. - Guard initialization with an early return when an init flag already exists in storage.
- Use multi-signature governance and a timelock delay before upgrades take effect.
- Emit an event on every upgrade/admin change so it can be monitored off-chain.