/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
$ npx -y skills add PlamenTSV/plamen --skill object-ownership --agent claude-codeHow 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.mdname: "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
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 | |--------|-----------------------------|---------------------|--
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.
Repo: PlamenTSV/plamen
Other skills on plamen.
- /ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents
Open skill - /bit-shift-safety
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case
Open skill - /centralization-risk
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen...
Open skill - /cross-chain-timing
Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external
Open skill - /dependency-audit
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external
Open skill - /economic-design-audit
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)
Open skill

