Agent skill
type-safety
Trigger Pattern Always (Aptos Move) - generic type exploitation - Inject Into Breadth agents, depth-state-trace
Install this agent skill to your Project
npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/aptos/type-safety
SKILL.md
TYPE_SAFETY Skill
Trigger Pattern: Always (Aptos Move) --- generic type exploitation Inject Into: Breadth agents, depth-state-trace
Move's type system is its primary security mechanism. Generic type parameters allow modules to be polymorphic, but incorrect or insufficient type constraints enable attackers to substitute unexpected types, bypass access control, confuse token types, or exploit phantom type assumptions. This skill audits every generic interface for type safety violations.
STEP PRIORITY: Steps 2 (Type Parameter Substitution) and 5 (Coin/FungibleAsset Type Confusion) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (3, 4) before skipping 2 or 5.
1. Generic Function Inventory
Enumerate ALL public, public(friend), and entry functions with generic type parameters:
| Function | Module | Type Params | Constraints | Visibility | Entry? | Who Can Call |
|---|---|---|---|---|---|---|
withdraw<T> |
vault | T | key |
public | YES | Any signer |
swap<X, Y> |
dex | X, Y | store |
public | YES | Any signer |
MANDATORY GREP: Search all .move files for fun .*< to find every generic function. Include internal (fun), public(friend) fun, public fun, and public entry fun.
For each generic function, additionally note:
- Does the function create, destroy, or transfer instances of the generic type?
- Does the function make assumptions about the generic type beyond its constraints? (e.g., assuming T is a coin type when the constraint is only
store) - Is the generic parameter used as a phantom/tag or does the function operate on actual instances of T?
2. Type Parameter Substitution Analysis
For each generic function identified in Step 1, analyze what happens when an attacker substitutes an unexpected type:
2a. Substitution Attack Table
| Function | Type Param | Expected Type | Attacker Substitutes | Guard Against Wrong Type? | Impact |
|---|---|---|---|---|---|
withdraw<T>(store) |
T | RealCoin | FakeCoin (attacker-defined) | YES --- {mechanism} / NO | {impact} |
Attack methodology per function:
- Identify expected type: What type does the protocol developer intend callers to use? This is often documented but NOT enforced at the type level.
- Check enforcement: Is there an on-chain mechanism that restricts T to the expected type? Common mechanisms:
- Registered type list (module stores
TypeInfoand checks against it) - Type witness parameter (function also requires
&TypeWitness<T>) - Module-level resource check (
assert!(exists<Pool<T>>(@protocol), E_INVALID_TYPE)) coin::is_coin_initialized<T>()check- Signer-of-defining-module pattern (only the module that defines T can call)
- Registered type list (module stores
- If NO enforcement: What happens if attacker creates
module attacker::fake { struct FakeCoin has store {} }and callswithdraw<FakeCoin>()?
2b. Cross-Pool / Cross-Market Type Confusion
For protocols with pools, markets, or vaults parameterized by type:
| Pool/Market | Type Parameter | Can Attacker Create Pool With Arbitrary Type? | Impact If Confusion |
|---|---|---|---|
Pool<T> |
T | YES --- anyone can call create_pool<T>() / NO |
{drain, mispricing, accounting error} |
Check: If Pool<RealCoin> and Pool<FakeCoin> exist, can operations on one affect the other? Common issues:
- Shared global state accessed by both pools
- Price oracle shared between pools (attacker manipulates FakeCoin price, affects RealCoin pool)
- Reward distribution computed across all pools regardless of type
Tag: [TRACE:substitute T=FakeCoin → {function} → {bypass/confusion} → impact: {X}]
3. Phantom Type Audit
For structs with phantom type parameters (phantom T):
3a. Phantom Type Inventory
| Struct | Phantom Param | Purpose | Runtime Impact of T | Can T Be Forged? |
|---|---|---|---|---|
Pool<phantom CoinType> |
CoinType | Type-tag discrimination | None (phantom) | {analysis} |
Phantom type rules in Move:
- Phantom type parameters do NOT affect runtime representation --- two structs with different phantom types have the same memory layout.
- Phantom types are used for type-level tagging:
Pool<USDC>vsPool<WETH>are different types at the Move level but identical at the bytecode level. - The compiler enforces that phantom types are not used in non-phantom positions.
Check for each phantom type:
- Is the phantom parameter used ONLY for type discrimination (correct use)?
- Does any function extract or operate on the phantom type at runtime? (should be impossible by compiler, but verify no workarounds)
- Can an attacker create a struct with a phantom type that aliases an existing legitimate phantom type? (e.g., creating
Pool<AttackerCoin>that interacts withPool<USDC>state) - Are phantom type parameters properly propagated through nested generics? (
Wrapper<phantom T>containingInner<T>--- is T phantom in Inner too?)
3b. Phantom Type Bypass Patterns
| Pattern | Risk | Check |
|---|---|---|
| Phantom used for access control | Medium | Can attacker define their own type to bypass access gate? |
| Phantom used for pool isolation | High | Does pool isolation rely solely on phantom type discrimination? |
| Phantom type in event emission | Low | Can attacker emit events with spoofed phantom types for off-chain confusion? |
4. Type Witness Pattern
For functions that accept type witnesses:
4a. Witness Inventory
| Witness Struct | Creating Module | Who Can Create? | Functions That Accept It | Properly Gated? |
|---|---|---|---|---|
TypeWitness<T> |
{module} | {analysis} | {list} | YES/NO |
Type witness pattern is Move's equivalent of capability-based access control at the type level. A type witness is a struct that can only be created by the module that defines the associated type. Functions that require a witness parameter are restricted to callers authorized by that module.
Check for each witness:
- Is the witness struct defined in the SAME module as the type it witnesses? If not, the witness can be created by anyone who imports the witness module.
- Does the witness have
drop? If yes, it can be created once and reused --- is this intended? - Does the witness have
copy? If yes, it can be duplicated --- does this break single-use assumptions? - Does the witness have
store? If yes, it can be persisted --- can an attacker store a witness and replay it later? - Is witness creation gated by signer checks or capability pattern? Or can any function in the defining module create it?
4b. Witness Forgery Analysis
For each witness used for access control:
1. TypeWitness<T> is required by function F
2. TypeWitness<T> can be created by: {list of functions/modules}
3. Can attacker reach a creation path? {YES/NO --- trace}
4. If YES: attacker creates witness and calls F with unauthorized type T
5. Impact: {unauthorized operation}
5. Coin/FungibleAsset Type Confusion
For all functions that handle Coin<T> or FungibleAsset:
5a. Coin Type Enforcement
| Function | Accepts | Type Restriction | Enforcement Mechanism | Bypass Possible? |
|---|---|---|---|---|
deposit<T>(coin: Coin<T>) |
Coin<T> | T must be registered | assert!(is_registered<T>()) |
{analysis} |
Check for each coin-handling function:
- Does the function verify that T is the expected coin type? Or does it accept ANY
Coin<T>? - If the function interacts with a pool/vault typed by T, does it verify the coin type matches the pool type?
- Can an attacker deposit
Coin<FakeCoin>and withdrawCoin<RealCoin>? - For multi-coin functions (
swap<X, Y>): are X and Y validated to be a supported pair? Can attacker swap between arbitrary types?
5b. FungibleAsset Metadata Confusion
For protocols using the Aptos Fungible Asset standard:
| Function | Metadata Check | Object Address Validated? | Impact If Wrong Metadata |
|---|---|---|---|
| {function} | YES --- assert!(metadata == expected) / NO |
YES/NO | {wrong asset deposited/withdrawn} |
Aptos FA-specific checks:
FungibleAssetis NOT parameterized by type --- it uses a metadata object address for discrimination. This means type-level enforcement does NOT apply. All FungibleAssets have the same Move type.- Does the function verify the metadata object matches the expected asset? If not, any FungibleAsset can be passed.
- Can an attacker create a FungibleAsset with spoofed metadata (same name/symbol as a legitimate asset)?
- Are
FungibleStoreaddresses properly validated when reading balances?
Tag: [TRACE:deposit FakeCoin to Pool<RealCoin> → withdraw RealCoin → drain pool]
5c. Mixed Standard Confusion
For protocols that handle BOTH Coin<T> and FungibleAsset:
| Operation | Which Standard Used? | Consistent? | Can Attacker Force Wrong Standard? |
|---|---|---|---|
| deposit | Coin<T> | --- | --- |
| withdraw | FungibleAsset | MISMATCH | {analysis} |
Check: If deposit uses Coin<T> but withdraw uses FungibleAsset (or vice versa), is the accounting consistent? The Aptos framework provides conversion between Coin<T> and FungibleAsset, but the conversion path may not be symmetric or may bypass module-level accounting.
6. Module Type Authority
Only the module that defines a struct can create instances of it. This is Move's module encapsulation guarantee. Audit for violations:
6a. Instance Creation Audit
| Struct | Defining Module | Public Functions That Return New Instances | Should Creation Be Public? |
|---|---|---|---|
| {name} | {module} | {list or NONE} | YES/NO --- {reason} |
Check for each struct:
- Does ANY
publicorpublic(friend)function return a newly created instance of this struct? - If yes, should external callers be able to obtain new instances? For value-bearing structs (coins, shares, receipts), uncontrolled creation = minting vulnerability.
- For structs used as capabilities or proofs: is there a public function that creates and returns them to arbitrary callers?
- For
friendfunctions that create instances: are ALL friend modules trusted to create instances responsibly?
6b. Friend Module Trust Analysis
| Module | Friend Modules | What Friends Can Create | Trust Justified? |
|---|---|---|---|
| {module} | {friends list} | {structs accessible via friend functions} | {analysis} |
Check: The friend declaration grants the friend module full access to public(friend) functions, including creation functions. If a friend module has a vulnerability, it can be used to mint/create unauthorized instances.
MANDATORY: For each friend relationship, verify: if the friend module is compromised (has a vulnerability), what is the maximum damage to the declaring module? If friend can create value-bearing structs → severity minimum HIGH.
Finding Template
When this skill identifies an issue:
**ID**: [TS-N]
**Severity**: [based on type confusion impact --- fund loss from wrong type = Critical]
**Step Execution**: check1,2,3,4,5,6 | X(reasons) | ?(uncertain)
**Rules Applied**: [R1:Y, R4:Y, R5:Y, R10:Y]
**Depth Evidence**: [TRACE:substitute T=X → bypass → impact], [BOUNDARY:type=FakeCoin]
**Location**: module::function (source_file.move:LineN)
**Title**: [Function] accepts arbitrary type [T] without [validation], enabling [type confusion/drain/forgery]
**Description**: [Trace from attacker type substitution through function logic to impact]
**Impact**: [Fund drain, unauthorized minting, accounting corruption, pool confusion]
Step Execution Checklist (MANDATORY)
CRITICAL: You MUST report completion status for ALL sections. Steps 2 and 5 are highest priority.
| Section | Required | Completed? | Notes |
|---|---|---|---|
| 1. Generic Function Inventory | YES | Y/X/? | MANDATORY --- grep ALL .move files |
| 2. Type Parameter Substitution | YES | Y/X/? | MANDATORY --- highest-severity source |
| 2b. Cross-Pool Type Confusion | IF pools/markets parameterized by type | Y/X(N/A)/? | |
| 3. Phantom Type Audit | IF phantom types used | Y/X(N/A)/? | |
| 4. Type Witness Pattern | IF witness pattern used | Y/X(N/A)/? | |
| 5. Coin/FA Type Confusion | YES | Y/X/? | MANDATORY --- fund loss vector |
| 5b. FungibleAsset Metadata | IF FA standard used | Y/X(N/A)/? | Metadata validation |
| 5c. Mixed Standard Confusion | IF both Coin and FA used | Y/X(N/A)/? | |
| 6. Module Type Authority | YES | Y/X/? | Creation function audit |
| 6b. Friend Module Trust | IF friend declarations exist | Y/X(N/A)/? | Friend compromise analysis |
Cross-Reference Markers
After Section 2 (Type Parameter Substitution):
- IF type substitution enables unauthorized access -> cross-reference with
ABILITY_ANALYSIS.mdSection 7 for ability constraint gaps - IF no enforcement mechanism found -> severity minimum HIGH for value-handling functions
After Section 5 (Coin/FA Type Confusion):
- Cross-reference with token flow analysis for entry/exit point type validation
- IF FungibleAsset used without metadata check -> severity minimum HIGH (any FA can be deposited)
- IF mixed Coin + FA standards -> verify accounting consistency across standards
After Section 6 (Module Type Authority):
- IF public creation function for value-bearing struct -> severity minimum CRITICAL (unauthorized minting)
- IF friend module has known vulnerability -> escalate all friend-accessible creation to HIGH minimum
Mandatory Forced Output
For Sections 2 and 5, you MUST produce output even if no issues found:
Section 2 Output (always required):
### 2. Type Parameter Substitution Analysis
| Function | Type Param | Expected Type | Enforcement | Substitution Blocked? |
|----------|-----------|---------------|-------------|----------------------|
| {function} | T | {expected} | {mechanism or NONE} | YES/NO |
**If enforcement = NONE for any value-handling function**: Finding verdict minimum PARTIAL.
Section 5 Output (always required):
### 5. Coin/FungibleAsset Type Confusion
| Function | Standard | Type Restriction | Enforcement | Bypass Possible? |
|----------|---------|-----------------|-------------|-----------------|
| {function} | Coin/FA | {restriction} | {mechanism} | YES/NO |
**If ANY coin-handling function lacks type enforcement**: Verdict CONFIRMED, severity based on fund impact.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
integration-hazard-research
Protocol Type Trigger NAMED_EXTERNAL_PROTOCOL (detected when recon finds import/interface for an identifiable external protocol — not standard libraries). Researches known integration hazards of the target protocol.
outcome-determinism
Protocol Type Trigger outcome_determinism - detected when EITHER of these code patterns are present - - Selection from finite depletable pool with fallback behavior (while(full)...
governance-attack-vectors
Protocol Type Trigger governance (detected when Governor, Timelock, voting, proposal, quorum, delegate patterns found) - Inject Into Breadth agents, depth-external, depth-edge-case
vault-accounting
Protocol Type Trigger vault (detected in recon TASK 0 Step 1) - Inject Into Core state agent OR economic design agent (merge via M4 hierarchy)
lending-protocol-security
Protocol Type Trigger lending (detected when recon finds liquidate|borrow|repay|collateral|lend|loan|LTV|healthFactor|interestRate|debtToken) - Inject Into Breadth agents, depth...
dex-integration-security
Protocol Type Trigger dex_integration (detected when recon finds swap|addLiquidity|removeLiquidity|IUniswapV2Router|ISwapRouter|amountOutMin|amountOutMinimum|slippage - AND the...
Didn't find tool you were looking for?