ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth…
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.
/fungible-asset-securityContext 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
name: "fungible-asset-security" description: "Trigger FA_STANDARD flag detected (protocol uses FungibleAsset standard) - Used by Breadth agents, depth-token-flow"
> **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
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.
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.
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:
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.
Audit FungibleStore creation, ownership chains, and access control:
| 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.
| 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.
| 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.
If the protocol uses dispatchable FungibleAsset (custom `withdraw`, `deposit`, or `derived_balance` hooks):
| 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 |
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
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth…
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into…
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) -…
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by…
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via…