Agent skill

package-version-safety

Trigger Pattern PACKAGE_UPGRADE flag (UpgradeCap detected, multiple package versions, upgrade policy references) - Inject Into Breadth agents, depth-external

Stars 215
Forks 33

Install this agent skill to your Project

npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/sui/package-version-safety

SKILL.md

Skill: PACKAGE_VERSION_SAFETY (Sui)

Trigger Pattern: PACKAGE_UPGRADE flag (UpgradeCap detected, multiple package versions, upgrade policy references) Inject Into: Breadth agents, depth-external Finding prefix: [PV-N] Rules referenced: R4, R8, R9, R10

Sui packages are immutable once published. "Upgrading" a package means publishing a NEW version at a NEW on-chain address, linked to the original via the UpgradeCap lineage. The old version's code remains callable forever. This creates a fundamentally different upgrade risk model compared to EVM proxies: instead of replacing logic in-place, Sui packages accumulate versions -- and shared objects may be accessible by ALL versions simultaneously.


Trigger Patterns

UpgradeCap|upgrade_policy|package::make_immutable|compatible|additive|dep_only|version|
migrate|old_version|new_version

Step 1: Upgrade Policy Inventory

For each package in scope:

# Package UpgradeCap Location UpgradeCap Holder Has store? Upgrade Policy Destroyed?
1 {pkg_name} {init function:line} {address / shared / wrapped in governance} YES/NO {compatible/additive/dep_only} YES (immutable) / NO

Checks:

  • Where is UpgradeCap stored?

    • Owned by deployer address: Single point of failure. Key loss -> permanent immutability. Key theft -> attacker can upgrade.
    • Shared object: DANGEROUS -- anyone can pass it to upgrade functions.
    • Wrapped in governance object: Good pattern -- upgrade requires governance approval.
    • Destroyed via make_immutable(): Package is permanently immutable. No upgrade risk.
    • Transferred to @0x0 or burn address: Effectively immutable.
  • Can UpgradeCap be transferred?

    • UpgradeCap has key + store by default -> freely transferable via public_transfer.
    • Is there a custom wrapper restricting transfer? (e.g., GovernanceCap wrapping UpgradeCap)
    • If transferable and held by EOA -> attacker stealing key can transfer UpgradeCap.
  • Can UpgradeCap be destroyed?

    • sui::package::make_immutable(cap) consumes UpgradeCap -> permanent immutability.
    • If UpgradeCap has drop via wrapper -> accidental destruction possible.

1b. UpgradeCap Governance Assessment

Governance Model Risk Level Assessment
Single EOA CRITICAL One key compromise replaces all package logic
Multisig (2/3 or lower) HIGH Low collusion threshold
Multisig (3/5+) MEDIUM Requires majority collusion
Multisig + timelock LOW Users can exit before malicious upgrade takes effect
DAO/governance contract LOW Distributed control, but check voter distribution
Destroyed (immutable) NONE Cannot upgrade, but also cannot patch bugs

Step 2: Version Consistency Check

For shared objects created by this package:

Shared Object Created By (Version) Current Version Field? V1 Functions Access? V2 Functions Access? Consistency Risk
{obj_type} V1 init() YES: version: u64 / NO {list funcs} {list funcs} {describe}

What happens when package is upgraded?

  • Existing shared objects created by V1 remain at their original address
  • V2 functions CAN access V1-created shared objects (types are preserved in compatible upgrades)
  • V1 functions are STILL callable and CAN access the same shared objects
  • This dual-access is the primary version safety concern

Can old-version and new-version calls on same shared object create inconsistency?

  • V1 function writes field A based on formula F1
  • V2 function writes field A based on formula F2
  • User calls V1 then V2 in separate transactions -> field A has inconsistent state
  • Especially dangerous: V2 adds a new check that V1 lacks. Attacker calls V1 to bypass V2's check.

Step 3: Dependency Version Pinning

For each dependency in Move.toml:

Dependency Source Pinned To Immutable? Upgrade Risk
Sui Framework sui = "..." {git rev or latest} Upgraded by validators Framework upgrade could change behavior
MoveStdlib MoveStdlib = "..." {git rev} Upgraded with framework Same as above
{third_party} {git url or on-chain} {specific rev / branch / on-chain version} YES/NO {describe}

Checks:

  • Are third-party dependencies pinned to specific git revisions? If pinned to main -> upstream changes included on recompile.
  • For on-chain published dependencies: is the dependency package immutable? If it has active UpgradeCap -> behavior can change.
  • Can a dependency upgrade break our package's invariants?
  • Are there transitive dependencies with their own upgrade risks?

Can dependency upgrade break our package?

  • Compatible dependency upgrade: function implementations can change but signatures preserved. Our calls still compile but behavior may differ.
  • Additive dependency upgrade: only new functions/types added. Existing behavior frozen.
  • Framework upgrades: sui::* packages upgraded by validators. Can change Move VM behavior, gas costs, object model rules.

Step 4: Type Compatibility Across Versions

When package V2 adds new types or fields:

Type V1 Definition V2 Changes Compatible Upgrade Rule Migration Needed?
{struct_name} {fields} {cannot change for compatible} Struct layouts FROZEN NO -- same layout
{new_struct} N/A {new in V2} New types allowed N/A

Sui type rules for compatible upgrades:

  • Existing struct field layouts CANNOT change (enforced by validator during upgrade)
  • New structs CAN be added
  • Existing function signatures CANNOT change
  • Function bodies CAN change (this is where logic vulnerabilities occur)
  • Generic type parameters must remain the same

Can V1 objects be used with V2 functions?

  • YES for compatible upgrades: types are identical, V2 functions accept V1 objects.
  • NO for separate package deployment: different package address = different types.

Can V2 objects be used with V1 functions?

  • V2 does not create new object types that V1 knows about (V1 code is frozen).
  • But V2 functions can modify shared objects that V1 functions then read -- state corruption possible.

Dynamic field implications:

  • Dynamic fields keyed by type. If V2 changes key/value types for dynamic fields -> V1-era entries orphaned.
  • Check: does V2 change any dynamic field key types?

Step 5: Upgrade Migration Safety

Does the package have migration functions to update shared objects from V1->V2 state?

Migration Function Trigger What It Updates Reversible? Access Control
{migrate_func} {admin call / automatic} {version field, new state} NO {AdminCap / anyone}

Version guard pattern: Shared objects contain version: u64. V1 functions check assert!(version == 1). V2 migration function sets version = 2. After migration, V1 functions abort because version != 1.

Check:

  • Is version guard implemented? If NOT -> old functions remain callable indefinitely -> FINDING.
  • Can migration be triggered by unauthorized parties?
  • Is migration atomic? Can it be partially completed?
  • What happens to user-owned objects during migration? (Owned objects cannot be modified by admin migration)

Step 6: UpgradeCap Governance

If UpgradeCap is owned by a single address:

Risk Description Severity
Full logic replacement Attacker upgrades package with malicious code. All shared objects now interact with attacker's logic. CRITICAL
Subtle parameter change Attacker upgrades to change a fee calculation or threshold in function body. Hard to detect. HIGH
Dependency manipulation Attacker upgrades to change dependency versions, pulling in vulnerable code. HIGH
Policy escalation blocked Upgrade policies can only be tightened (compatible -> additive -> dep_only -> immutable). Attacker cannot escalate from additive to compatible. Mitigation

Mitigations to check:

  • Is UpgradeCap behind multisig?
  • Is there an upgrade timelock (users can exit before upgrade takes effect)?
  • Is there a multi-party approval mechanism for upgrades?
  • Has the upgrade policy been tightened from the default compatible?
  • Is make_immutable() called in init() for packages that should never upgrade?

Key Questions (Must Answer All)

  1. UpgradeCap security: Who holds it? What is the attack surface if compromised?
  2. Upgrade policy: Is the policy appropriate? Could it be tightened without losing needed functionality?
  3. Cross-version bypass: Can old functions bypass security checks added in new versions?
  4. Version guard: Is there a mechanism to disable old functions after upgrade?
  5. Type compatibility: Are all types compatible? Are dynamic fields accessible across versions?

Common False Positives

  1. Immutable package: UpgradeCap destroyed or make_immutable called -> no upgrade risk
  2. No shared objects: If old and new packages share no objects, cross-version interaction impossible
  3. Version guard implemented: Shared objects check version field, old functions abort after migration
  4. Capability migrated: Old package's capabilities consumed by new package, old functions uncallable
  5. additive or dep_only policy: Existing logic frozen (but new functions can still access shared objects -- check Step 3c)

Output Schema

markdown
## Finding [PV-N]: Title

**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5,6 | skip(reason) | uncertain
**Rules Applied**: [R4:___, R8:___, R9:___, R10:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN

**Upgrade Risk Type**: UPGRADECAP_MANAGEMENT / POLICY_INAPPROPRIATE / CROSS_VERSION_BYPASS / TYPE_INCOMPATIBILITY / DEPENDENCY_RISK / MISSING_VERSION_GUARD
**Package Version**: V{N} -> V{N+1}
**Shared Objects Affected**: {list}

**Description**: What is wrong
**Impact**: What can happen (logic replacement, security bypass, stranded assets, type mismatch)
**Evidence**: Code showing vulnerability
**Recommendation**: How to fix (tighten policy, add version guard, migrate capabilities, destroy UpgradeCap)

Step Execution Checklist (MANDATORY)

Step Required Completed? Notes
1. Upgrade Policy Inventory YES UpgradeCap location, holder, policy
1b. UpgradeCap Governance Assessment YES Risk level for each package
2. Version Consistency Check YES All shared objects checked for dual-version access
3. Dependency Version Pinning YES Move.toml analyzed
4. Type Compatibility Across Versions YES Dynamic fields included
5. Upgrade Migration Safety YES Version guard pattern checked
6. UpgradeCap Governance IF single-address holder Multisig/timelock/approval checks

Cross-Reference Markers

After Step 1: If UpgradeCap held by single address -> immediate finding (minimum HIGH).

After Step 2: If cross-version bypass possible -> cross-reference with MIGRATION_ANALYSIS Step 5 for shared object function enumeration.

After Step 3: Feed dependency risks to DEPENDENCY_AUDIT for transitive dependency analysis.

After Step 5: If no version guard AND shared objects hold user funds -> minimum HIGH finding.

If any step skipped, document valid reason (N/A, package is immutable, no shared objects, no third-party dependencies).

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results