Aptos Specialized Scanner
Specialized security scanner for Aptos Move smart contracts. Extends the general Move Scanner with Aptos-specific patterns, framework modules, and the global storage model.
Why a Separate Aptos Scanner?
While the Move Scanner covers language-level patterns shared between Aptos and Sui, Aptos has a fundamentally different storage model (global resources under addresses), framework (AptosFramework), and upgrade system that require dedicated detection rules.
| Feature |
Aptos |
Sui |
| Storage |
Global resources under addresses |
Object model |
| Resource access |
move_to, borrow_global, move_from |
Passed as function parameters |
| Upgrade |
Module upgrade with compatibility policy |
Package upgrade with UpgradeCap |
| Tokens |
aptos_framework::coin |
sui::coin with TreasuryCap |
| Accounts |
Account + AuthenticationKey |
No account concept |
| Randomness |
aptos_framework::randomness (commit-reveal) |
sui::random |
Detection Capabilities
| Category |
Detection |
Severity |
| Resource Safety |
Resource created but never stored (move_to missing) |
High |
| Resource Safety |
borrow_global_mut without authorization check |
Critical |
| Resource Safety |
move_from extracting resource without ownership proof |
Critical |
| Resource Safety |
Missing exists<T>(addr) check before access |
Medium |
| Abilities |
Value-holding type with copy ability (duplication) |
Critical |
| Abilities |
Capability with drop (can be silently discarded) |
High |
| Upgrade |
Module upgrade authority is single EOA |
High |
| Upgrade |
compatible upgrade policy on critical module |
Medium |
| Coin |
MintCapability stored in publicly accessible location |
Critical |
| Coin |
CoinStore registration not checked before deposit |
Medium |
| Auth |
Missing signer parameter on privileged entry function |
Critical |
| Auth |
signer::address_of() not compared to authorized address |
High |
| Auth |
Resource account SignerCapability exposed publicly |
Critical |
| Storage |
Table/SimpleMap with unbounded growth |
Medium |
| Storage |
acquires annotation missing (compile-time, but indicates design) |
Low |
| Events |
State change without event emission |
Low |
Aptos Framework Security-Critical Modules
| Module |
Functions to Audit |
Key Risk |
aptos_framework::coin |
initialize, mint, burn, transfer, register |
Cap management |
aptos_framework::account |
create_account, rotate_authentication_key |
Auth key rotation |
aptos_framework::resource_account |
create_resource_account, retrieve_resource_account_cap |
Signer cap leak |
aptos_framework::object |
create_object, transfer, generate_signer |
Object ownership |
aptos_framework::fungible_asset |
mint, burn, transfer, deposit, withdraw |
New token standard |
aptos_framework::multisig_account |
create, execute_transaction |
Multisig logic |
aptos_framework::staking_contract |
create_staking_contract, distribute |
Reward calculation |
aptos_framework::governance |
create_proposal, vote |
Voting power |
Common Aptos Vulnerability Examples
Resource Account Signer Capability Leak
// CRITICAL: SignerCapability stored with 'store' ability allows extraction
struct ResourceAccountCap has key, store {
signer_cap: account::SignerCapability,
}
// If anyone can get a reference to this struct, they can create a signer
// for the resource account and drain all its assets
public fun get_resource_signer(cap: &ResourceAccountCap): signer {
account::create_signer_with_capability(&cap.signer_cap)
}
// SAFE: No public accessor, internal only
struct ResourceAccountCap has key {
signer_cap: account::SignerCapability,
}
fun internal_get_signer() acquires ResourceAccountCap {
let cap = borrow_global<ResourceAccountCap>(@resource_addr);
let signer = account::create_signer_with_capability(&cap.signer_cap);
// Use signer internally only
}
Coin Registration Race Condition
// VULNERABLE: Depositing without checking CoinStore registration
public fun distribute_rewards(recipients: &vector<address>) {
let i = 0;
while (i < vector::length(recipients)) {
let addr = *vector::borrow(recipients, i);
// ABORTS if addr doesn't have CoinStore<RewardToken> registered!
coin::deposit(addr, reward_coins);
i = i + 1;
};
}
// SAFE: Check registration first
public fun distribute_rewards(recipients: &vector<address>) {
let i = 0;
while (i < vector::length(recipients)) {
let addr = *vector::borrow(recipients, i);
if (coin::is_account_registered<RewardToken>(addr)) {
coin::deposit(addr, reward_coins);
} else {
// Handle: skip, queue for later, or register for them
};
i = i + 1;
};
}
Resources
Workflows
See Also
Error Code Reference
Aptos-specific error codes and framework abort codes. Aptos uses the Move abort system with standard error categories.
Aptos Error Categories (std::error)
| Category |
Constant |
Hex Prefix |
Meaning |
INVALID_ARGUMENT |
1 |
0x1____ |
Bad input parameter |
OUT_OF_RANGE |
2 |
0x2____ |
Value outside acceptable range |
NOT_FOUND |
6 |
0x6____ |
Resource or item not found |
ALREADY_EXISTS |
8 |
0x8____ |
Resource or item already exists |
PERMISSION_DENIED |
5 |
0x5____ |
Insufficient permissions |
RESOURCE_EXHAUSTED |
9 |
0x9____ |
Limit reached (e.g., max supply) |
UNAVAILABLE |
13 |
0xD____ |
Temporarily unavailable |
Aptos Framework Errors
| Abort Code |
Module |
Meaning |
0x10006 |
coin |
Coin store not registered for address |
0x10007 |
coin |
Insufficient coin balance |
0x80001 |
account |
Account already exists |
0x80002 |
account |
Account not found |
0x50001 |
table |
Key already exists |
0x50002 |
table |
Key not found |
0x60001 |
coin |
Coin amount is zero |
0x90001 |
resource_account |
Resource account already exists |
ENOT_OWNER |
Common |
Signer is not the owner — access control check |
ENOT_AUTHORIZED |
Common |
Lacking required authorization |
Aptos Token / NFT Errors
| Abort Code |
Module |
Meaning |
ETOKEN_NOT_FOUND |
token |
Token or collection does not exist |
ECOLLECTION_NOT_FOUND |
token |
Collection does not exist |
EINSUFFICIENT_BALANCE |
token |
Token balance too low for operation |
ENOT_CREATOR |
token |
Caller is not the collection creator |
EFIELD_NOT_MUTABLE |
token |
Attempting to modify immutable field |
Troubleshooting
| Issue |
Likely Cause |
Solution |
| Global storage vulnerabilities missed |
Scanner doesn't audit borrow_global / move_to patterns |
Map all global storage operations; check exists<T> before borrow_global and move_to |
| Resource account risks not flagged |
Scanner doesn't track SignerCapability lifecycle |
Trace resource_account::create_resource_account and verify SignerCapability storage/access |
| Module upgrade attack surface ignored |
Scanner only checks current code |
Verify UpgradePolicy (immutable vs compatible); check who holds the UpgradeCap |
| View functions not audited |
Scanner focuses on entry functions |
View functions can leak sensitive state; audit all #[view] functions for information disclosure |
| Event emission gaps not detected |
Scanner doesn't check event coverage |
Verify all state-changing operations emit events for off-chain tracking |
| Coin type confusion not caught |
Scanner trusts Move type system |
Verify all coin operations use correct type parameters; check for CoinType aliasing |
1---2name: aptos-scanner3description: Use when the user wants to audit Aptos Move smart contracts, scan Aptos-specific patterns including global storage model, resource accounts, or coin modules, review Aptos DeFi protocols for framework module interaction vulnerabilities, or analyze Aptos-specific upgrade and governance patterns.4---56# Aptos Specialized Scanner78Specialized security scanner for Aptos Move smart contracts. Extends the general [Move Scanner](../move-scanner/SKILL.md) with Aptos-specific patterns, framework modules, and the global storage model.910---1112## Why a Separate Aptos Scanner?1314While the Move Scanner covers language-level patterns shared between Aptos and Sui, Aptos has a fundamentally different **storage model** (global resources under addresses), **framework** (AptosFramework), and **upgrade system** that require dedicated detection rules.1516| Feature | Aptos | Sui |17|---------|-------|-----|18| Storage | Global resources under addresses | Object model |19| Resource access | `move_to`, `borrow_global`, `move_from` | Passed as function parameters |20| Upgrade | Module upgrade with compatibility policy | Package upgrade with UpgradeCap |21| Tokens | `aptos_framework::coin` | `sui::coin` with TreasuryCap |22| Accounts | Account + AuthenticationKey | No account concept |23| Randomness | `aptos_framework::randomness` (commit-reveal) | `sui::random` |2425---2627## Detection Capabilities2829| Category | Detection | Severity |30|----------|-----------|----------|31| **Resource Safety** | Resource created but never stored (`move_to` missing) | High |32| **Resource Safety** | `borrow_global_mut` without authorization check | Critical |33| **Resource Safety** | `move_from` extracting resource without ownership proof | Critical |34| **Resource Safety** | Missing `exists<T>(addr)` check before access | Medium |35| **Abilities** | Value-holding type with `copy` ability (duplication) | Critical |36| **Abilities** | Capability with `drop` (can be silently discarded) | High |37| **Upgrade** | Module upgrade authority is single EOA | High |38| **Upgrade** | `compatible` upgrade policy on critical module | Medium |39| **Coin** | `MintCapability` stored in publicly accessible location | Critical |40| **Coin** | `CoinStore` registration not checked before deposit | Medium |41| **Auth** | Missing `signer` parameter on privileged entry function | Critical |42| **Auth** | `signer::address_of()` not compared to authorized address | High |43| **Auth** | Resource account `SignerCapability` exposed publicly | Critical |44| **Storage** | `Table`/`SimpleMap` with unbounded growth | Medium |45| **Storage** | `acquires` annotation missing (compile-time, but indicates design) | Low |46| **Events** | State change without event emission | Low |4748---4950## Aptos Framework Security-Critical Modules5152| Module | Functions to Audit | Key Risk |53|--------|--------------------|----------|54| `aptos_framework::coin` | `initialize`, `mint`, `burn`, `transfer`, `register` | Cap management |55| `aptos_framework::account` | `create_account`, `rotate_authentication_key` | Auth key rotation |56| `aptos_framework::resource_account` | `create_resource_account`, `retrieve_resource_account_cap` | Signer cap leak |57| `aptos_framework::object` | `create_object`, `transfer`, `generate_signer` | Object ownership |58| `aptos_framework::fungible_asset` | `mint`, `burn`, `transfer`, `deposit`, `withdraw` | New token standard |59| `aptos_framework::multisig_account` | `create`, `execute_transaction` | Multisig logic |60| `aptos_framework::staking_contract` | `create_staking_contract`, `distribute` | Reward calculation |61| `aptos_framework::governance` | `create_proposal`, `vote` | Voting power |6263---6465## Common Aptos Vulnerability Examples6667### Resource Account Signer Capability Leak6869```move70// CRITICAL: SignerCapability stored with 'store' ability allows extraction71struct ResourceAccountCap has key, store {72 signer_cap: account::SignerCapability,73}7475// If anyone can get a reference to this struct, they can create a signer76// for the resource account and drain all its assets77public fun get_resource_signer(cap: &ResourceAccountCap): signer {78 account::create_signer_with_capability(&cap.signer_cap)79}8081// SAFE: No public accessor, internal only82struct ResourceAccountCap has key {83 signer_cap: account::SignerCapability,84}8586fun internal_get_signer() acquires ResourceAccountCap {87 let cap = borrow_global<ResourceAccountCap>(@resource_addr);88 let signer = account::create_signer_with_capability(&cap.signer_cap);89 // Use signer internally only90}91```9293### Coin Registration Race Condition9495```move96// VULNERABLE: Depositing without checking CoinStore registration97public fun distribute_rewards(recipients: &vector<address>) {98 let i = 0;99 while (i < vector::length(recipients)) {100 let addr = *vector::borrow(recipients, i);101 // ABORTS if addr doesn't have CoinStore<RewardToken> registered!102 coin::deposit(addr, reward_coins);103 i = i + 1;104 };105}106107// SAFE: Check registration first108public fun distribute_rewards(recipients: &vector<address>) {109 let i = 0;110 while (i < vector::length(recipients)) {111 let addr = *vector::borrow(recipients, i);112 if (coin::is_account_registered<RewardToken>(addr)) {113 coin::deposit(addr, reward_coins);114 } else {115 // Handle: skip, queue for later, or register for them116 };117 i = i + 1;118 };119}120```121122---123124## Resources125- [Aptos Patterns](resources/aptos-patterns.md)126127## Workflows128- [Aptos Audit](workflows/aptos-audit.md)129130## See Also131- [Move Scanner](../move-scanner/SKILL.md) for general Move patterns132- [Chain Guide: Aptos](../chain-guides/aptos.md) for chain-specific context133134## Error Code Reference135136Aptos-specific error codes and framework abort codes. Aptos uses the Move abort system with standard error categories.137138### Aptos Error Categories (std::error)139140| Category | Constant | Hex Prefix | Meaning |141|----------|---------|-----------|----------|142| `INVALID_ARGUMENT` | `1` | `0x1____` | Bad input parameter |143| `OUT_OF_RANGE` | `2` | `0x2____` | Value outside acceptable range |144| `NOT_FOUND` | `6` | `0x6____` | Resource or item not found |145| `ALREADY_EXISTS` | `8` | `0x8____` | Resource or item already exists |146| `PERMISSION_DENIED` | `5` | `0x5____` | Insufficient permissions |147| `RESOURCE_EXHAUSTED` | `9` | `0x9____` | Limit reached (e.g., max supply) |148| `UNAVAILABLE` | `13` | `0xD____` | Temporarily unavailable |149150### Aptos Framework Errors151152| Abort Code | Module | Meaning |153|-----------|--------|----------|154| `0x10006` | `coin` | Coin store not registered for address |155| `0x10007` | `coin` | Insufficient coin balance |156| `0x80001` | `account` | Account already exists |157| `0x80002` | `account` | Account not found |158| `0x50001` | `table` | Key already exists |159| `0x50002` | `table` | Key not found |160| `0x60001` | `coin` | Coin amount is zero |161| `0x90001` | `resource_account` | Resource account already exists |162| `ENOT_OWNER` | Common | Signer is not the owner — access control check |163| `ENOT_AUTHORIZED` | Common | Lacking required authorization |164165### Aptos Token / NFT Errors166167| Abort Code | Module | Meaning |168|-----------|--------|----------|169| `ETOKEN_NOT_FOUND` | `token` | Token or collection does not exist |170| `ECOLLECTION_NOT_FOUND` | `token` | Collection does not exist |171| `EINSUFFICIENT_BALANCE` | `token` | Token balance too low for operation |172| `ENOT_CREATOR` | `token` | Caller is not the collection creator |173| `EFIELD_NOT_MUTABLE` | `token` | Attempting to modify immutable field |174175## Troubleshooting176177| Issue | Likely Cause | Solution |178|-------|-------------|----------|179| Global storage vulnerabilities missed | Scanner doesn't audit `borrow_global` / `move_to` patterns | Map all global storage operations; check `exists<T>` before `borrow_global` and `move_to` |180| Resource account risks not flagged | Scanner doesn't track `SignerCapability` lifecycle | Trace `resource_account::create_resource_account` and verify `SignerCapability` storage/access |181| Module upgrade attack surface ignored | Scanner only checks current code | Verify `UpgradePolicy` (immutable vs compatible); check who holds the `UpgradeCap` |182| View functions not audited | Scanner focuses on entry functions | View functions can leak sensitive state; audit all `#[view]` functions for information disclosure |183| Event emission gaps not detected | Scanner doesn't check event coverage | Verify all state-changing operations emit events for off-chain tracking |184| Coin type confusion not caught | Scanner trusts Move type system | Verify all coin operations use correct type parameters; check for `CoinType` aliasing |