agents/skills/sui/type-safety/SKILL.md
Trigger Pattern Always (Sui Move) -- generic type exploitation - Inject Into Breadth agents, depth-state-trace
npx skillsauth add plamentsv/plamen type-safetyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Trigger Pattern: Always (Sui Move) -- generic type exploitation Inject Into: Breadth agents, depth-state-trace
For every generic function and parameterized type in the protocol:
STEP PRIORITY: Steps 4 (OTW Analysis) and 6 (Coin/Balance Type Safety) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections before skipping 4 or 6.
Enumerate ALL functions with type parameters across all modules:
| Module | Function | Type Params | Constraints | Public? | Phantom? | Notes |
|--------|----------|-------------|-------------|---------|----------|-------|
| {mod} | {func} | <T>, <T: store>, etc. | {ability constraints} | YES/NO | YES/NO | {context} |
Sui type parameter semantics:
<T> -- unconstrained. T can be ANY type. Maximally permissive.<T: key + store> -- T must be an object that can be freely transferred.<T: drop> -- T can be discarded. Often used for witness patterns.<phantom T> -- T is not used at runtime, only for type distinction (e.g., Coin<phantom T>). No ability constraints enforced on phantom params at the struct level.For each generic function, verify constraints are sufficient:
| Function | Param | Constraint | Actually Used As | Sufficient? |
|----------|-------|-----------|-----------------|-------------|
| {func} | T | none | stored in dynamic field (needs store) | NO |
| {func} | T | store | used as Coin<T> balance | NO -- needs further check |
Check: For each type parameter, trace how it is actually used in the function body:
storekeydrop| Function | Param | Constraint | Actually Needed | Over-Constrained? |
|----------|-------|-----------|----------------|-------------------|
| {func} | T | key + store + drop | only store | YES -- limits usability |
Note: Over-constraining is not a security issue but limits composability. Document as Informational.
| Struct | Phantom Param | Used in Runtime Logic? | Type-Level Distinction Sound? |
|--------|--------------|----------------------|------------------------------|
| {struct} | phantom T | YES (BUG) / NO | YES/NO |
Rule: Phantom type parameters MUST NOT be used in runtime field types (non-phantom positions). If a phantom param is used in a non-phantom position, the compiler rejects it. But check: is the phantom param providing meaningful type distinction, or can an attacker substitute any type?
For every function that destructures a mutable reference or binds &mut field references, verify assignments update values rather than rebinding references:
| Function | Source of &mut | Ref Variables | Assignment | Uses *lhs = *rhs? | Wrong Field Possible? |
|----------|------------------|---------------|------------|--------------------|-----------------------|
Checks:
&mut Struct, treat destructured fields as references, not copied values.left = limit rebinds the local reference; it does not copy limit into the left field. Confirm the intended code uses *left = *limit or equivalent.*ref = ... writes. Do they now write to the original field or to another field?Identify all witness patterns (structs used for one-time authorization):
| Witness Type | Module | Created In | Consumed In | Abilities | Singleton? |
|-------------|--------|------------|-------------|-----------|-----------|
| {name} | {mod} | {function} | {function} | drop only / none | YES/NO |
Witness security checks:
new / constructor paths.)store -> it can persist beyond its intended scope -> FINDING.)copy -> it can be reused -> FINDING.)For each witness type:
Can an attacker construct this witness type?
1. Is the struct definition public (`public struct`)? -> External modules CAN create instances if fields are accessible
2. Does the struct have fields? -> If no fields (unit struct), only the defining module can create it
3. Are all field types accessible to external modules? -> If yes, external construction possible
4. Is construction gated by `init` or capability? -> Check the gate
Rule: A witness with a public struct definition and publicly-accessible field types is forgeable from external modules. This is a CRITICAL finding if the witness gates value creation (coin minting, capability issuance, etc.).
Identify all OTW patterns:
| Module | OTW Type | init Signature | OTW Consumed? | Package Upgrade Safe? |
|--------|----------|-----------------|---------------|----------------------|
| {mod} | {MODULE_NAME} (uppercase) | init(otw: MODULE_NAME, ctx: &mut TxContext) | YES/NO | YES/NO |
Sui OTW rules:
drop ability (and typically no other abilities).init on module publish.init (typically passed to coin::create_currency or similar).Security checks:
drop ability? If it has copy -> can be duplicated (should be impossible due to Sui's OTW rules, but verify). If it has store -> can be persisted past init -> FINDING.init? If stored instead of consumed -> it can be reused.init. Is the protocol relying on init for setup that should be repeatable? If the module uses OTW to create a TreasuryCap or Publisher, those are one-time-only.T: drop as a witness without verifying it is the actual OTW type -> can be called with any drop-able type.Check if the protocol uses sui::types::is_one_time_witness<T>() to verify OTW:
| Function | Accepts Generic Witness? | OTW Verification? | Bypass Possible? |
|----------|------------------------|-------------------|-----------------|
| {func} | <T: drop>(witness: T) | is_one_time_witness(&witness) / NO | YES/NO |
Rule: Any public function that accepts a generic witness <T: drop> without calling is_one_time_witness can be called with any droppable type, not just the actual OTW. This is a HIGH finding if the function creates currencies, capabilities, or other privileged objects.
Model attacks where an attacker substitutes an unexpected type:
For each public generic function:
Can an attacker call function<MaliciousType>() where the protocol expects function<ExpectedType>()?
1. What type does the protocol intend?
2. What constraints prevent substitution?
3. What happens if a different type is passed?
| Function | Expected Type | Constraint | Substitute Possible? | Impact |
|----------|--------------|-----------|---------------------|--------|
| {func} | SUI | none (just <T>) | YES -- any type | {impact} |
| {func} | USDC | <T: store> | YES -- any store type | {impact} |
| {func} | specific coin | runtime check on CoinMetadata | NO | N/A |
For each generic struct:
Pool<T> { balance: Balance<T>, ... }
Can an attacker create Pool<FakeToken> and interact with Pool<RealToken>'s functions?
Check: Does the protocol use type parameters to distinguish pools/vaults? If yes:
Pool<A> and Pool<B> fully isolated?Pool<A> by exploiting Pool<FakeA>?Table<TypeName, PoolConfig>)Move generics prove that Pool<BTC> holds BTC, but they do NOT prove that a runtime selector, config row, position, or receipt supplied in the same call belongs to that generic type. For every public/package function accepting both generic type parameters and runtime identity inputs, build this table:
| Function | Generic Type(s) | Runtime Selector / Config / Object | Binding Check | Missing Binding Impact |
|----------|-----------------|------------------------------------|---------------|------------------------|
| withdraw<T> | T | asset: u8, Pool<T> | type_name::get<T>() == config.coin_type? | wrong asset accounting / wrong pool withdrawal |
| liquidate<X,Y,SX> | SX | Position, SupplyPool<X,SX> | pool ID or debt-share type matches position? | free collateral / debt not repaid |
Checks:
T plus an asset index/config ID, does it validate type_name::get<T>() against the stored config row?T, does the function derive it instead of trusting a user-supplied index?object::id(pool) == stored_pool_id or equivalent?X, Y, SX, LP), does the function bind all of them to the stored position/config, not just the coin type?Specific analysis for Coin<T> and Balance<T> patterns:
| Function | Accepts Coin<T> | T Verified? | Verification Method | Bypass? |
|----------|-------------------|-------------|--------------------|---------:|
| {func} | YES | YES/NO | {method or NONE} | YES/NO |
Sui Coin safety model:
Coin<T> is parameterized by the coin type T.Coin<T> requires a TreasuryCap<T>, which is created via OTW in init.Coin<SUI> because they don't have TreasuryCap<SUI>.Coin<ATTACKER_TOKEN> and pass it to a function expecting Coin<T> if T is generic.Checks:
Coin<SUI>) or generic (Coin<T>)?Balance<FakeToken> be joined with Balance<RealToken>? (NO -- type system prevents this. But verify no unsafe transmutation exists.)Coin split/merge operations type-safe? (coin::split preserves T.)| Operation | Input Type | Output Type | Type Preserved? | Accounting Impact |
|-----------|-----------|-------------|----------------|------------------|
| deposit | Coin<T> | Balance<T> (internal) | YES/NO | {impact if mismatch} |
| withdraw | Balance<T> (internal) | Coin<T> | YES/NO | {impact if mismatch} |
| swap | Coin<A> -> Coin<B> | both types | YES/NO | {impact if mismatch} |
Check: At every point where Balance<T> is converted to/from Coin<T>, is the type parameter T consistent? The compiler enforces this for concrete types, but for generic functions operating on Balance<T>, trace that T remains the same throughout the flow.
Analyze Publisher object usage:
| Module | Publisher Created? | Used For | Stored/Shared? | Transfer Restricted? | |--------|-------------------|----------|---------------|---------------------| | {mod} | YES/NO | {display, transfer policy, etc.} | {how stored} | YES/NO |
Security checks:
Publisher proves package authorship. Functions that accept &Publisher trust the caller is the package publisher.Publisher stored in a shared object (accessible to anyone with the right reference)?Publisher be transferred to a malicious actor?Publisher from external callers? (These trust the caller is a publisher.)Publisher remains valid. Does the protocol account for this?**ID**: [TS-N]
**Severity**: [CRITICAL if coin forgery/capability bypass, HIGH if type confusion with value, MEDIUM if witness issue]
**Step Execution**: check1,2,3,4,5,6,7 | X(reasons) | ?(uncertain)
**Rules Applied**: [R4:Y, R5:Y, R10:Y, ...]
**Depth Evidence**: [VARIATION:T=FakeToken vs T=SUI], [TRACE:generic_fn<Attacker>->state_corruption]
**Location**: module::function
**Title**: [Type safety issue] in [function] enables [attack/bypass]
**Description**: [Specific type parameter exploitation path with concrete substitute type]
**Impact**: [Unauthorized coin minting, capability forgery, pool drainage, accounting corruption]
CRITICAL: You MUST report completion status for ALL sections. Findings with incomplete sections will be flagged for depth review.
| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 1. Generic Function Inventory | YES | Y/X/? | All modules |
| 2. Type Parameter Constraint Analysis | YES | Y/X/? | |
| 2c. Phantom Type Correctness | IF phantom params | Y/X(N/A)/? | |
| 2d. Mutable Reference Assignment Correctness | IF &mut destructuring/ref assignment | Y/X(N/A)/? | |
| 3. Type Witness Pattern Audit | IF witness patterns | Y/X(N/A)/? | |
| 3a. Witness Forgery Check | IF witness patterns | Y/X(N/A)/? | |
| 4. OTW Analysis | IF init with witness | Y/X(N/A)/? | HIGH PRIORITY |
| 4a. OTW Verification Pattern | IF generic witness functions | Y/X(N/A)/? | |
| 5. Generic Type Confusion Attacks | YES | Y/X/? | |
| 5b. Struct-Level Type Confusion | IF generic structs | Y/X(N/A)/? | |
| 5c. Generic Type to Config/Object Binding | IF generic + runtime selector/object | Y/X(N/A)/? | |
| 6. Coin/Balance Type Safety | IF Coin/Balance used | Y/X(N/A)/? | HIGH PRIORITY |
| 6b. Balance Accounting Type Safety | IF Balance used | Y/X(N/A)/? | |
| 7. Publisher and Package Authority | IF Publisher used | Y/X(N/A)/? | |
After Section 3 (Witness Pattern Audit):
After Section 4 (OTW Analysis):
initTreasuryCap or Publisher can be created multiple timesAfter Section 6 (Coin/Balance Type Safety):
After Section 7 (Publisher Authority):
development
Prepare Solidity projects for a security audit — test coverage, test quality, NatSpec docs, code hygiene, dependency health, best-practice enforcement, deployment readiness, and project documentation checks. Generates a scored Audit Readiness Report and optionally runs static analysis. Trigger on: "prepare for audit", "audit readiness", "pre-audit check", "audit prep", "NatSpec check", or any request to review a Solidity codebase before a security review.
development
Launch the Plamen deterministic Web3 security audit pipeline
development
Run the Plamen smart-contract audit wizard in Codex
testing
Launch the Plamen deterministic L1 infrastructure audit pipeline