Skip to content
Security
Skill

/object-ownership

Trigger Pattern Always required for Sui Move audits -- object lifecycle and ownership model - Inject Into Breadth agents, depth-state-trace, depth-token-flow

From plugin
plamen
276160 skills12 agents4 commands
Install
$ npx -y skills add PlamenTSV/plamen --skill object-ownership --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/object-ownership

Context preview

The summary Claude sees to decide when to auto-load this skill.

Trigger Pattern Always required for Sui Move audits -- object lifecycle and ownership model - Inject Into Breadth agents, depth-state-trace, depth-token-flow

SKILL.md

object-ownership.SKILL.md
name: "object-ownership"
description: "Trigger Pattern Always required for Sui Move audits -- object lifecycle and ownership model - Inject Into Breadth agents, depth-state-trace, depth-token-flow"

OBJECT_OWNERSHIP Skill

> **Trigger Pattern**: Always required for Sui Move audits -- object lifecycle and ownership model > **Inject Into**: Breadth agents, depth-state-trace, depth-token-flow > **Finding prefix**: `[OO-N]` > **Rules referenced**: R4, R5, R9, R10, R13

Sui's object-centric model is fundamentally different from account-based chains. Every struct with the `key` ability is an on-chain object with a globally unique ID, and its ownership model (owned/shared/frozen/wrapped) determines who can access and mutate it. Incorrect ownership choices, missing transfer restrictions, orphaned UIDs, and uncontrolled dynamic fields are the primary Sui-specific vulnerability classes.

---

1. Object Inventory

For EVERY struct with `key` ability in the codebase, build this table:

| # | Object Name (Module) | Abilities | Ownership Model | Created Where | Transferred Where | Destroyed Where | Has `store`? | |---|---------------------|-----------|-----------------|---------------|-------------------|-----------------|-------------| | 1 | {name} ({module}) | {key, store, ...} | OWNED / SHARED / FROZEN / WRAPPED / MIXED | {function:line} | {function:line or NEVER} | {function:line or NEVER} | YES/NO |

**Ability rules**:

  • `key` alone: Object can exist on-chain but CANNOT be transferred by generic `transfer::public_transfer` (requires module-defined transfer logic).
  • `key + store`: Object CAN be transferred by anyone via `transfer::public_transfer`. This is a **permissive** choice -- verify it is intentional.
  • `key + store + copy`: Object can be duplicated -- extremely rare for value-bearing objects. FLAG if found on any object holding balances.
  • `key + store + drop`: Object can be silently discarded without calling a destructor. FLAG if the object holds `Balance<T>` or other value -- tokens can be lost.

**Ownership model classification**:

  • **OWNED**: Created and transferred to a specific address via `transfer::transfer` or `transfer::public_transfer`. Only the owner can pass it as a transaction argument.
  • **SHARED**: Made accessible to all via `transfer::public_share_object`. Any transaction can read/write it. CRITICAL access control implications.
  • **FROZEN**: Made immutable via `transfer::public_freeze_object`. Anyone can read, no one can mutate.
  • **WRAPPED**: Stored as a field inside another object (not directly addressable on-chain). Accessible only through the parent.
  • **MIXED**: Object starts as one type and transitions to another (e.g., created as owned, then shared). Document the transition path.

---

2. Ownership Model Analysis

2a. Owned Object Audit

For each OWNED object:

| Object | Should Be Shared Instead? | Ownership Transfer Possible? | Transfer Restriction Correct? | Assumption Risk | |--------|--------------------------|-----------------------------|-----------------------------|----------------| | {name} | YES/NO ({reason}) | YES (has `store`) / NO (no `store`) | {analysis} | {risk if ownership changes} |

**Check patterns**:

  • **Should this be shared?** If multiple unrelated parties need to mutate the object in the same epoch, owned model creates bottlenecks or requires trust delegation. Common mistake: config objects that should be shared are kept owned, forcing single-admin bottleneck.
  • **Ownership change undermines assumptions?** If code assumes "only admin holds AdminCap", but AdminCap has `store` ability, it can be transferred to anyone. Verify that transfer does not break invariants downstream.
  • **Phantom ownership**: Object is "owned" but the owner address is a PDA-like derived address that nobody controls (e.g., `@0x0`). The object is effectively inaccessible -- equivalent to locked funds if it holds value.

2b. Shared Object Audit

For each SHARED object:

| Object | Mutation Functions | Access Guards | Concurrent Mutation Risk | Ordering Dependency | |--------|-------------------|---------------|------------------------|-------------------| | {name} | {list all functions that take `&mut` ref} | {what prevents unauthorized mutation} | YES/NO ({analysis}) | YES/NO ({analysis}) |

**CRITICAL checks**:

  • **Access control on mutation**: Shared objects can be passed as arguments by ANY transaction. If a function takes `&mut SharedObj` without verifying the caller has authority (e.g., checking a capability object), anyone can mutate it. This is the #1 Sui vulnerability pattern.
  • **Public mutable reference getters**: A `public` function that returns `&mut` internal state, calls `borrow_mut`, or exposes dynamic-field mutable access is externally callable by attacker packages. It must be `public(package)` or require a capability unless external mutation is explicitly safe.
  • **Helper visibility**: Internal helpers that mutate state or expose sensitive references should not be `public` only because another module in the same package needs them. Use `public(package)` for same-package helpers.
  • **Consensus ordering**: Transactions touching the same shared object are ordered by Sui's consensus. If the protocol relies on specific transaction ordering (e.g., "admin sets fee before user trades"), front-running is possible because consensus ordering is non-deterministic from the user's perspective.
  • **Race conditions**: Two transactions that both mutate the same shared object field can produce different final states depending on execution order. If the protocol assumes sequential access, this is a bug.
  • **Gas-based DoS**: An attacker can submit many transactions touching a shared object to increase contention and gas costs for legitimate users.

2c. Frozen Object Audit

For each FROZEN object:

| Object | Should Updates Be Possible? | Freezing Reversible? | Data Staleness Risk | |--------|-----------------------------|---------------------|--

Read more
Ships withplamen

Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.

Get the whole plugin