Proxy-upgradeable contract rug pattern — admin upgrade rights as a backdoor
On this page
Wiki route
This entry sits under security domain. Read it against bytecode forensic three-tier verify for the on-chain verification mechanics it depends on, and against ERC-4337 overview for the broader smart-account context where upgradeable proxies are now the default deployment shape.
[!info] TL;DR An upgradeable proxy splits a contract into a thin proxy (holds state + storage) and a swappable implementation (holds logic). Whoever controls the upgrade key can repoint the proxy to new logic at any time — including logic that drains, pauses, or blacklists. The “verified, audited” source a user reads is only the current implementation; the rug is the next one. This entry frames upgrade rights as a standing backdoor and gives a public, reproducible procedure to size that risk before depositing.
Mechanism: where the upgrade key lives
The proxy delegates all calls (delegatecall) to an implementation address stored at a fixed storage slot. ERC-1967 standardizes those slots so tools can find them deterministically:
| Slot purpose | Storage slot (ERC-1967) | Derivation |
|---|---|---|
| Implementation | 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc | keccak256('eip1967.proxy.implementation') - 1 |
| Admin | 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 | keccak256('eip1967.proxy.admin') - 1 |
| Beacon | 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50 | keccak256('eip1967.proxy.beacon') - 1 |
The - 1 trick yields a slot with no known keccak256 preimage, so application storage cannot accidentally collide with it. The admin slot is the rug surface: read it, and you know exactly who can swap the logic.
Two proxy shapes, two control locations
| Pattern | Upgrade logic lives in | Admin / upgrader | Forensic note |
|---|---|---|---|
| Transparent proxy | the proxy itself | ERC-1967 admin slot | Admin calls are routed to upgrade logic; non-admin calls fall through to implementation |
| UUPS (ERC-1822) | the implementation | a function (often upgradeTo) guarded by an onlyOwner-style modifier in the implementation | Lighter/cheaper, but the guard is in code that can itself be upgraded away — read the current implementation’s auth, not just the proxy |
| Beacon proxy | a shared beacon contract | beacon owner | A single beacon upgrade can repoint many proxies at once — blast radius is the whole family |
OpenZeppelin’s UUPSUpgradeable adds a guard that refuses upgrades to a non-UUPS-compliant implementation (to avoid bricking upgradeability), but that guard does not constrain what a permitted upgrader may install. Authorization is the only real control, and authorization is mutable.
The rug sequence
- Deploy a benign, audited implementation
V1. Get it verified on a block explorer and (ideally) Sourcify. Users read clean source. - Accumulate deposits / TVL while the upgrade key sits with an EOA or a 1-of-1 “multisig”.
- At chosen time, the upgrade key installs
V2containing a withdraw-all, mint, pause, or balance-rewrite path. - Execute, then optionally repoint back to
V1to muddy the post-mortem.
Steps 1 and 4 are why a point-in-time source read is insufficient — the malicious code may never be the currently shown implementation. This is the same “the shown artifact is not the binding artifact” failure mode covered for explorers in Etherscan verified-source poisoning.
Public risk-sizing checklist
Reproducible with a public RPC node and a block explorer; no private data required.
- Read the ERC-1967 admin slot (
eth_getStorageAt(proxy, <admin slot>)). Is the upgrader an EOA, a single-key wallet, or a real multisig? - If UUPS, fetch the current implementation and inspect the
upgradeTo/_authorizeUpgradeguard — who passes it? - Is there a timelock between scheduling and executing an upgrade? No timelock = instant rug capability.
- Is upgrade authority renounced, or held behind a known governance process? Compare against hook-enforced compliance where policy is bound into the call path rather than left to discretionary admin keys.
- Has the implementation address already changed since deployment? Block explorers and the ERC-1967
Upgraded(address)event log every prior implementation — enumerate them. - For beacon proxies, identify the beacon owner and how many proxies share it (blast radius).
When this matters most
- Bridges, vaults, and staking contracts holding pooled funds behind a single upgradeable proxy.
- “Audited” protocols where the audit covered
V1but the upgrade key is an unaudited EOA. - Agent-callable and account-abstraction contracts: smart accounts under ERC-4337 and delegated EOAs under ERC-7702 are frequently upgradeable, so the same admin-key analysis applies to the ERC-7715 agent payment stack before granting it spending permissions.
When NOT to over-index on this
- Genuinely immutable (non-proxy) contracts with no upgrade slot — there is no upgrade rug surface (other risks remain).
- Upgrades gated behind a long timelock plus a credible multisig — risk is materially lower (users can exit during the delay).
- Permissioned / non-EVM settings such as Canton, where contract evolution follows a template-governance path rather than an EVM proxy slot — analyze the governance route instead.
Related
- Security domain index
- Bytecode forensic three-tier verify
- Etherscan verified-source poisoning
- Fork-and-Rebrand five-layer audit framework
- ERC-4337 overview
- ERC-7702 overview
Sources
- ERC-1967: Proxy Storage Slots — standardized implementation/admin/beacon slots — https://eips.ethereum.org/EIPS/eip-1967
- ERC-1822: Universal Upgradeable Proxy Standard (UUPS) — upgrade logic in the implementation — https://eips.ethereum.org/EIPS/eip-1822
- OpenZeppelin Contracts — Proxy API (Transparent, UUPS, Beacon) — https://docs.openzeppelin.com/contracts/4.x/api/proxy
- ethereum.org — Upgrading smart contracts (proxy patterns overview) — https://ethereum.org/en/developers/docs/smart-contracts/upgrading/