Skip to content
Back to skills

Package Version Safety

ASecurity

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

  • 281 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added May 27, 2026
blockchaingogitsecurity

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add PlamenTSV/plamen --skill package-version-safety --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Package Version Safety?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Package Version Safety
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/plamentsv-package-version-safety/badge)](https://www.skillsdirectory.com/skills/plamentsv-package-version-safety)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: "package-version-safety"
description: "Trigger Pattern PACKAGE_UPGRADE flag (UpgradeCap detected, multiple package versions, upgrade policy references) - Inject Into Breadth agents, depth-external"
---

# 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).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.