Agent skill
centralization-risk
Trigger Pattern Protocol has privileged capabilities (AdminCap, OwnerCap, UpgradeCap, TreasuryCap, custom caps) - Inject Into Breadth agents (optional), depth-state-trace
Install this agent skill to your Project
npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/sui/centralization-risk
SKILL.md
Skill: CENTRALIZATION_RISK (Sui)
Trigger Pattern: Protocol has privileged capabilities (AdminCap, OwnerCap, UpgradeCap, TreasuryCap, custom caps) Inject Into: Breadth agents (optional), depth-state-trace Finding prefix:
[CR-N]Rules referenced: R2, R6, R9, R10, R13 Required: NO (recommended when protocol has 3+ distinct privileged capability types)
Covers: single points of failure, privilege escalation, capability object management, external governance dependencies, emergency powers. On Sui, centralization risk has unique dimensions: capability objects (AdminCap) are first-class owned objects that can be transferred, UpgradeCap controls full package replacement, TreasuryCap controls token supply, and shared objects can be the target of admin-gated mutations. The ownership and lifecycle of capability objects IS the access control model.
Trigger Patterns
AdminCap|OwnerCap|UpgradeCap|TreasuryCap|GovernanceCap|OperatorCap|PauserCap|MinterCap|
Cap\b|_cap|admin|authority|privilege
Step 1: Capability Inventory
Enumerate ALL capability objects and ALL functions requiring capabilities:
| # | Capability Type | Module | Abilities | Created Where | Holder | What It Controls | Impact If Lost/Stolen |
|---|---|---|---|---|---|---|---|
| 1 | {CapType} | {module} | {key, store, ...} | {init or func} | {address/shared} | {list functions} | {worst case} |
Key question for each capability: Is it OWNED (by a single address) or SHARED (accessible via reference in any transaction)?
- Owned AdminCap: Only the owner can pass it as a tx argument. Strongest access control, but single point of failure.
- Shared config with admin field: Admin address stored in shared object. More flexible but requires
assert!(sender == admin)checks -- verify these are present on ALL admin functions. - Shared capability object: DANGEROUS -- anyone can pass a shared
AdminCapas a transaction argument without ownership.
Categorize each by impact:
- FUND_CONTROL: Can move, lock, or destroy user funds (e.g., emergency withdraw, treasury drain)
- PARAMETER_CONTROL: Can change fees, rates, thresholds, delays (e.g., set_fee, set_max_leverage)
- OPERATIONAL_CONTROL: Can pause, unpause, add/remove pools, whitelist/blacklist
- UPGRADE_CONTROL: UpgradeCap -- controls package upgrades, policy changes
- MINT_CONTROL: TreasuryCap -- controls token supply (mint/burn)
Sui-specific ability checks:
- Does the capability have
storeability? If YES -> anyone holding it canpublic_transferit, meaning the privilege is freely transferable. Is this intentional? - Does the capability have
dropability? If YES -> it can be silently discarded. For AdminCap, dropping it means admin functions become permanently uncallable. For UpgradeCap, dropping makes the package permanently immutable. - Is the capability created ONLY in
init()? If created elsewhere, can unauthorized parties mint new capabilities?
Step 2: Capability Hierarchy and Separation
Map the capability hierarchy:
| Capability | Created By | Can Create Other Caps? | Transferable (has store)? |
Destructible (has drop)? |
Timelock/Delay? |
|---|---|---|---|---|---|
| {cap} | {init / admin_func} | YES/NO | YES/NO | YES/NO | YES/NO |
Check:
- Are FUND_CONTROL and UPGRADE_CONTROL separated into different capability types?
- Does any single capability type grant both PARAMETER_CONTROL and FUND_CONTROL?
- Are capability transfers behind timelocks or governance mechanisms?
- Can capabilities be destroyed, and what happens when they are?
- Is there a master capability that can create all other capabilities?
Sui-specific separation patterns:
- Best practice: UpgradeCap held by governance multisig, AdminCap held by operations team, TreasuryCap held by treasury
- Anti-pattern: single
initfunction creates ALL caps and transfers them totx_context::sender()-- single point of failure at deployment - Two-step transfer: propose new admin -> new admin accepts. Prevents accidental transfer to wrong address.
UpgradeCap Analysis (CRITICAL)
| Package | UpgradeCap Holder | Upgrade Policy | Destroyed? | Risk Level |
|---|---|---|---|---|
| {package_id} | {address or description} | {compatible/additive/dep_only} | YES (immutable) / NO | {assessment} |
Risk levels:
- UpgradeCap destroyed (
make_immutable): No upgrade risk. - UpgradeCap held by governance multisig with timelock: Low risk.
- UpgradeCap held by multisig (no timelock): Low-Medium risk.
- UpgradeCap held by single address: CRITICAL risk -- one compromised key replaces entire package. All shared objects now interact with attacker code.
- UpgradeCap stored in shared object: Check access control carefully. If extraction is possible -> same as single address risk.
Step 3: Single Points of Failure
For each capability type:
| Capability | Key Compromise Impact | Current Protection | Residual Risk |
|---|---|---|---|
| {cap} | {what attacker can do with it} | {multisig holder? timelock wrapper?} | {what remains} |
Sui-Specific SPOF Analysis
| Risk | Description | Severity |
|---|---|---|
| UpgradeCap compromise | Attacker publishes malicious upgrade. All shared objects now interact with attacker code. ALL user funds at risk. | CRITICAL if single address, HIGH if multisig without timelock |
| AdminCap compromise | Attacker calls admin functions: drain pools, change parameters, pause protocol. | HIGH if AdminCap controls fund extraction |
| TreasuryCap compromise | Attacker mints unlimited tokens, diluting all holders. | HIGH if supply-sensitive protocol |
AdminCap with store |
Holder (or compromised key) can transfer AdminCap to anyone via public_transfer. New holder has full admin access. |
Adds transfer risk to any compromise scenario |
AdminCap with drop |
Admin can accidentally destroy the capability. Admin functions become permanently uncallable. | MEDIUM -- permanent loss of admin access (Rule 9 if admin functions needed for user fund recovery) |
| Phantom ownership | Capability transferred to an address nobody controls (e.g., @0x0). Object is permanently inaccessible -- equivalent to destroying it. |
Same as destruction if holding value or needed for operations |
Severity assessment:
- Single address with FUND_CONTROL or UPGRADE_CONTROL -> HIGH (minimum)
- Multisig holds capability + timelock -> LOW (but document)
- UpgradeCap destroyed + no admin fund extraction -> INFO
Step 4: External Governance Dependencies
Identify parameters or behaviors controlled by EXTERNAL governance:
| Dependency | External Entity | What They Control | Protocol Impact If Changed | Notification? |
|---|---|---|---|---|
| {dep} | {entity} | {parameter/behavior} | {impact} | YES/NO |
Sui-specific external governance:
- Sui framework upgrades: Validators upgrade
sui::*packages via governance. Can framework changes break this protocol? - Oracle provider changes: If protocol reads from oracle shared object, oracle admin can change prices, feeds, or parameters
- DeFi protocol governance: External pools, vaults, or DEXes may change parameters
- Bridge governance: Wormhole guardian set rotation, Sui Bridge committee changes
- Dependency package upgrades: If a dependency has active UpgradeCap, its owner can publish new versions. Our package pins to specific version at compile time, but compatible upgrades preserve types that we import.
Check:
- Can external governance changes break protocol invariants?
- Does the protocol have circuit breakers for external changes?
- Does the protocol verify external package addresses at call sites? Types from compatible-upgraded packages remain the same, but behavior may change.
Step 5: Emergency Powers
Document emergency/pause capabilities:
| Emergency Function | Required Capability | What It Affects | Recovery Path | Time to Recover |
|---|---|---|---|---|
| {func} | {cap_type} | {scope: all operations / specific pool} | {how to resume} | {estimate} |
Sui Emergency Patterns
| Pattern | Description | Risk |
|---|---|---|
| Global pause field | Shared config object has paused: bool. All user functions check it. |
Standard -- check: can users withdraw when paused? |
| Capability destruction | Admin destroys their own capability to "renounce" control. | Irreversible -- if needed later for recovery, funds stranded |
| Object freeze | Admin calls transfer::public_freeze_object on a config. Permanent immutability. |
If done to wrong object, permanent loss of admin access |
| Package policy tightening | UpgradeCap holder restricts policy (compatible -> additive -> immutable). | Good for security, but irreversible. Cannot loosen policy. |
Check:
- Can pausing strand user funds permanently? (Rule 9 -- stranded asset severity floor: minimum MEDIUM)
- Is there a maximum pause duration or automatic unpause?
- Can users emergency-withdraw during pause?
- What happens if the PauserCap/AdminCap is lost or destroyed?
- Can the protocol be permanently bricked by destroying a critical capability?
- If no exit during pause -> apply Rule 9 (minimum MEDIUM)
Output Schema
## Finding [CR-N]: Title
**Verdict**: CONFIRMED / PARTIAL / REFUTED
**Step Execution**: check1,2,3,4,5 | skip(reason) | uncertain
**Rules Applied**: [R2:___, R6:___, R9:___, R10:___, R13:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN
**Centralization Type**: FUND_CONTROL / PARAMETER_CONTROL / OPERATIONAL_CONTROL / UPGRADE_CONTROL / MINT_CONTROL
**Affected Capability**: {cap_type}
**Mitigation Present**: {multisig / timelock / UpgradeCap destroyed / governance / NONE}
**Description**: What is wrong
**Impact**: What can happen if capability is compromised, lost, or holder acts maliciously
**Recommendation**: How to mitigate (destroy UpgradeCap, use multisig, add timelock, remove `store` ability, wrap in governance)
Step Execution Checklist (MANDATORY)
| Step | Required | Completed? | Notes |
|---|---|---|---|
| 1. Capability Inventory (all cap-gated functions) | YES | Owned vs shared, abilities checked | |
| 2. Capability Hierarchy and Separation | YES | store/drop analysis, UpgradeCap assessment |
|
| 3. Single Points of Failure (per capability) | YES | ||
| 4. External Governance Dependencies | YES | ||
| 5. Emergency Powers and Recovery Paths | YES |
Cross-Reference Markers
After Step 1: Cross-reference with ABILITY_ANALYSIS Section 4 (Capability Pattern Audit) -- capabilities with store enable unrestricted transfer.
After Step 2: If UpgradeCap held by single address -> immediate finding (minimum HIGH).
After Step 3: If AdminCap has drop and is needed for fund recovery -> Rule 9 stranded asset finding.
After Step 5: If no emergency withdraw exists AND pause is possible -> Rule 9 stranded asset finding.
After Step 5: If protocol claims trustlessness but retains UpgradeCap/AdminCap -> Rule 13 anti-normalization finding.
If any step skipped, document valid reason (N/A, no external governance, no emergency functions, single capability only).
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
integration-hazard-research
Protocol Type Trigger NAMED_EXTERNAL_PROTOCOL (detected when recon finds import/interface for an identifiable external protocol — not standard libraries). Researches known integration hazards of the target protocol.
outcome-determinism
Protocol Type Trigger outcome_determinism - detected when EITHER of these code patterns are present - - Selection from finite depletable pool with fallback behavior (while(full)...
governance-attack-vectors
Protocol Type Trigger governance (detected when Governor, Timelock, voting, proposal, quorum, delegate patterns found) - Inject Into Breadth agents, depth-external, depth-edge-case
vault-accounting
Protocol Type Trigger vault (detected in recon TASK 0 Step 1) - Inject Into Core state agent OR economic design agent (merge via M4 hierarchy)
lending-protocol-security
Protocol Type Trigger lending (detected when recon finds liquidate|borrow|repay|collateral|lend|loan|LTV|healthFactor|interestRate|debtToken) - Inject Into Breadth agents, depth...
dex-integration-security
Protocol Type Trigger dex_integration (detected when recon finds swap|addLiquidity|removeLiquidity|IUniswapV2Router|ISwapRouter|amountOutMin|amountOutMinimum|slippage - AND the...
Didn't find tool you were looking for?