Severity: Critical
A two-step admin transfer contract where accept_admin() reads the pending admin from storage and writes them as the new admin — but never calls pending.require_auth(). Any address can call accept_admin() and seize control of the contract.
- Admin proposes a new admin via
propose_admin(new_admin). - Before the intended pending admin can accept, a random attacker calls
accept_admin(). - The attacker takes over as admin — the intended target never signed or authorised the transfer.
pub fn accept_admin(env: Env) {
let pending: Address = env.storage().persistent().get(&DataKey::PendingAdmin).expect("no pending admin");
// ❌ Missing: pending.require_auth();
env.storage().persistent().set(&DataKey::Admin, &pending);
env.storage().persistent().remove(&DataKey::PendingAdmin);
}pub fn accept_admin(env: Env) {
let pending: Address = env.storage().persistent().get(&SecureDataKey::PendingAdmin).expect("no pending admin");
// ✅ Require the pending admin to authorise the transfer.
pending.require_auth();
env.storage().persistent().set(&SecureDataKey::Admin, &pending);
env.storage().persistent().remove(&SecureDataKey::PendingAdmin);
}See the inline secure.rs module inside this crate for the full corrected implementation.