/fungible-asset-security
Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow
$ npx -y skills add PlamenTSV/plamen --skill fungible-asset-security --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
/fungible-asset-security
Context preview
The summary Claude sees to decide when to auto-load this skill.
Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow
SKILL.md
fungible-asset-security.SKILL.mdname: "fungible-asset-security"
description: "Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow"
Skill: FUNGIBLE_ASSET_SECURITY
> **Trigger**: FA_STANDARD flag detected (protocol uses FungibleAsset standard) > **Used by**: Breadth agents, depth-token-flow > **Covers**: FungibleAsset metadata validation, zero-value exploitation, store ownership, dispatchable hooks, Ref safety, Coin-to-FA migration
Purpose
Audit FungibleAsset standard usage for Aptos-specific vulnerabilities. The FA standard introduces object-based token management with capabilities (MintRef, BurnRef, TransferRef, FreezeRef) and optional dispatchable hooks. Incorrect usage creates counterfeit token acceptance, forced transfers, reentrancy, and accounting mismatches.
Methodology
STEP 1: Metadata Validation Audit
For EVERY function that accepts a `FungibleAsset` parameter or reads from a `FungibleStore`:
| # | Function | Accepts FA/Reads Store | Validates Metadata? | Expected Metadata | Bypass Possible? | |---|----------|----------------------|--------------------|--------------------|-----------------| | 1 | {func} | FungibleAsset param | YES/NO | {expected_metadata_obj} | YES/NO |
**How metadata validation works**:
// CORRECT: validates the asset is the expected type
let metadata = fungible_asset::metadata(&fa);
assert!(metadata == expected_metadata, ERROR_WRONG_ASSET);
// VULNERABLE: no validation - accepts ANY FungibleAsset
public fun deposit(fa: FungibleAsset) {
// Attacker can pass a worthless FA created from their own metadata
fungible_asset::deposit(store, fa);
}**MANDATORY SEARCH**: Grep all `.move` files for: 1. `FungibleAsset` in function signatures (parameters) 2. For each hit: trace whether `fungible_asset::metadata(&fa)` is called and compared 3. Functions that ONLY use `fungible_asset::amount(&fa)` without metadata check -> FLAG
**Severity**: Accepting unvalidated FungibleAsset = accepting counterfeit tokens. If the function credits the user or modifies protocol state based on the FA amount -> HIGH/CRITICAL.
STEP 2: Zero-Value Exploitation
Analyze zero-value FungibleAsset paths:
| # | Zero-Value Source | Code Path Triggered | State Modified? | Cleanup Correct? | |---|------------------|-------------------|----------------|-----------------| | 1 | `fungible_asset::zero(metadata)` | {trace what happens} | YES/NO | YES/NO | | 2 | Withdrawal of 0 amount | {trace} | YES/NO | YES/NO |
**Check for each**: 1. Can `fungible_asset::zero(metadata)` be used to trigger code paths that modify state? (e.g., register a user, set a flag, emit an event) 2. Does `fungible_asset::destroy_zero(fa)` clean up properly, or does it leave dangling state? 3. Can zero-value deposits/withdrawals:
- Register a new FungibleStore where one shouldn't exist?
- Trigger reward distribution checkpoints?
- Bypass minimum deposit requirements (checked after or before deposit)?
- Create entries in tracking data structures (SmartTable, vector)?
4. Does `amount == 0` get explicitly checked and rejected at entry points?
**Pattern**: Zero-value operations often bypass `amount > 0` checks that were assumed but never written, allowing state modifications without economic cost.
STEP 3: Store Creation and Ownership Analysis
Audit FungibleStore creation, ownership chains, and access control:
3a. Store Creation Inventory
| Store Type | Created By | Creation Permissionless? | Owner | Can Attacker Create? | |-----------|-----------|-------------------------|-------|---------------------| | Primary store | `primary_fungible_store::ensure_primary_store_exists()` | YES - anyone can create for any address | Address owner | YES (for any address) | | Custom store | `fungible_asset::create_store()` on ConstructorRef | Only during object construction | Object owner | Depends on who can construct |
**CRITICAL**: `primary_fungible_store::ensure_primary_store_exists(addr, metadata)` is permissionless. An attacker can create a primary store for ANY address for ANY metadata. If the protocol assumes a store's existence means the user has interacted with the protocol -> FINDING.
3b. Transitive Ownership
| Object A | Owns Object B | B Has FungibleStore | A Can Withdraw from B? | |----------|-------------|--------------------|-----------------------| | {object} | {child_object} | YES/NO | YES - via object ownership chain |
**Check**: If Object A owns Object B which owns a FungibleStore, the owner of Object A can withdraw from B's store through the ownership chain. Trace all object ownership hierarchies for unintended fund access paths.
3c. Store Address Confusion
| Function | Expects Store At | Actually Reads From | Match? | |----------|-----------------|--------------------|---------| | {func} | Protocol-controlled store | User-supplied address | VERIFY |
**Pattern**: Protocol calculates expected store address but user can supply a different store address. If the function doesn't verify the store belongs to the expected object/address -> FINDING.
STEP 4: Dispatchable Hook Analysis
If the protocol uses dispatchable FungibleAsset (custom `withdraw`, `deposit`, or `derived_balance` hooks):
4a. Hook Inventory
| Hook Type | Registered? | Implementation Module | Can Reenter? | Can Revert? | Can Manipulate? | |-----------|-------------|---------------------|-------------|------------|-----------------| | withdraw | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE | | deposit | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE | | derived_balance | YES/NO | {module::func} | ANALYZE | N/A | ANALYZE |
4b. Reentrancy via Hooks
For each registered hook: 1. Does the hook call back into the registering module's public functions? 2. Does the hook call into any other module that reads/writes shared state? 3. Is `#[module_lock]` applied to the registering module? (prevents indirect reentrancy but N
Read more
name: "fungible-asset-security" description: "Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow"
Skill: FUNGIBLE_ASSET_SECURITY
> **Trigger**: FA_STANDARD flag detected (protocol uses FungibleAsset standard) > **Used by**: Breadth agents, depth-token-flow > **Covers**: FungibleAsset metadata validation, zero-value exploitation, store ownership, dispatchable hooks, Ref safety, Coin-to-FA migration
Purpose
Audit FungibleAsset standard usage for Aptos-specific vulnerabilities. The FA standard introduces object-based token management with capabilities (MintRef, BurnRef, TransferRef, FreezeRef) and optional dispatchable hooks. Incorrect usage creates counterfeit token acceptance, forced transfers, reentrancy, and accounting mismatches.
Methodology
STEP 1: Metadata Validation Audit
For EVERY function that accepts a `FungibleAsset` parameter or reads from a `FungibleStore`:
| # | Function | Accepts FA/Reads Store | Validates Metadata? | Expected Metadata | Bypass Possible? | |---|----------|----------------------|--------------------|--------------------|-----------------| | 1 | {func} | FungibleAsset param | YES/NO | {expected_metadata_obj} | YES/NO |
**How metadata validation works**:
// CORRECT: validates the asset is the expected type
let metadata = fungible_asset::metadata(&fa);
assert!(metadata == expected_metadata, ERROR_WRONG_ASSET);
// VULNERABLE: no validation - accepts ANY FungibleAsset
public fun deposit(fa: FungibleAsset) {
// Attacker can pass a worthless FA created from their own metadata
fungible_asset::deposit(store, fa);
}**MANDATORY SEARCH**: Grep all `.move` files for: 1. `FungibleAsset` in function signatures (parameters) 2. For each hit: trace whether `fungible_asset::metadata(&fa)` is called and compared 3. Functions that ONLY use `fungible_asset::amount(&fa)` without metadata check -> FLAG
**Severity**: Accepting unvalidated FungibleAsset = accepting counterfeit tokens. If the function credits the user or modifies protocol state based on the FA amount -> HIGH/CRITICAL.
STEP 2: Zero-Value Exploitation
Analyze zero-value FungibleAsset paths:
| # | Zero-Value Source | Code Path Triggered | State Modified? | Cleanup Correct? | |---|------------------|-------------------|----------------|-----------------| | 1 | `fungible_asset::zero(metadata)` | {trace what happens} | YES/NO | YES/NO | | 2 | Withdrawal of 0 amount | {trace} | YES/NO | YES/NO |
**Check for each**: 1. Can `fungible_asset::zero(metadata)` be used to trigger code paths that modify state? (e.g., register a user, set a flag, emit an event) 2. Does `fungible_asset::destroy_zero(fa)` clean up properly, or does it leave dangling state? 3. Can zero-value deposits/withdrawals:
- Register a new FungibleStore where one shouldn't exist?
- Trigger reward distribution checkpoints?
- Bypass minimum deposit requirements (checked after or before deposit)?
- Create entries in tracking data structures (SmartTable, vector)?
4. Does `amount == 0` get explicitly checked and rejected at entry points?
**Pattern**: Zero-value operations often bypass `amount > 0` checks that were assumed but never written, allowing state modifications without economic cost.
STEP 3: Store Creation and Ownership Analysis
Audit FungibleStore creation, ownership chains, and access control:
3a. Store Creation Inventory
| Store Type | Created By | Creation Permissionless? | Owner | Can Attacker Create? | |-----------|-----------|-------------------------|-------|---------------------| | Primary store | `primary_fungible_store::ensure_primary_store_exists()` | YES - anyone can create for any address | Address owner | YES (for any address) | | Custom store | `fungible_asset::create_store()` on ConstructorRef | Only during object construction | Object owner | Depends on who can construct |
**CRITICAL**: `primary_fungible_store::ensure_primary_store_exists(addr, metadata)` is permissionless. An attacker can create a primary store for ANY address for ANY metadata. If the protocol assumes a store's existence means the user has interacted with the protocol -> FINDING.
3b. Transitive Ownership
| Object A | Owns Object B | B Has FungibleStore | A Can Withdraw from B? | |----------|-------------|--------------------|-----------------------| | {object} | {child_object} | YES/NO | YES - via object ownership chain |
**Check**: If Object A owns Object B which owns a FungibleStore, the owner of Object A can withdraw from B's store through the ownership chain. Trace all object ownership hierarchies for unintended fund access paths.
3c. Store Address Confusion
| Function | Expects Store At | Actually Reads From | Match? | |----------|-----------------|--------------------|---------| | {func} | Protocol-controlled store | User-supplied address | VERIFY |
**Pattern**: Protocol calculates expected store address but user can supply a different store address. If the function doesn't verify the store belongs to the expected object/address -> FINDING.
STEP 4: Dispatchable Hook Analysis
If the protocol uses dispatchable FungibleAsset (custom `withdraw`, `deposit`, or `derived_balance` hooks):
4a. Hook Inventory
| Hook Type | Registered? | Implementation Module | Can Reenter? | Can Revert? | Can Manipulate? | |-----------|-------------|---------------------|-------------|------------|-----------------| | withdraw | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE | | deposit | YES/NO | {module::func} | ANALYZE | ANALYZE | ANALYZE | | derived_balance | YES/NO | {module::func} | ANALYZE | N/A | ANALYZE |
4b. Reentrancy via Hooks
For each registered hook: 1. Does the hook call back into the registering module's public functions? 2. Does the hook call into any other module that reads/writes shared state? 3. Is `#[module_lock]` applied to the registering module? (prevents indirect reentrancy but N
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

