security-audit

This skill performs systematic security audits of Move contracts using a comprehensive checklist. Every item must pass before deployment.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "security-audit" with this command: npx skills add iskysun96/aptos-agent-skills/iskysun96-aptos-agent-skills-security-audit

Security Audit Skill

Overview

This skill performs systematic security audits of Move contracts using a comprehensive checklist. Every item must pass before deployment.

Critical: Security is non-negotiable. User funds depend on correct implementation.

Core Workflow

Step 1: Run Security Checklist

Review ALL categories in order:

  • Access Control - Who can call functions?

  • Input Validation - Are inputs checked?

  • Object Safety - Object model used correctly?

  • Reference Safety - No dangerous references exposed?

  • Arithmetic Safety - Overflow/underflow prevented?

  • Generic Type Safety - Phantom types used correctly?

  • Testing - 100% coverage achieved?

Step 2: Access Control Audit

Verify:

  • All entry functions verify signer authority

  • Object ownership checked with object::owner()

  • Admin functions check caller is admin

  • Function visibility uses least-privilege

  • No public functions modify state without checks

Check for:

// ✅ CORRECT: Signer verification public entry fun update_config(admin: &signer, value: u64) acquires Config { let config = borrow_global<Config>(@my_addr); assert!(signer::address_of(admin) == config.admin, E_NOT_ADMIN); // Safe to proceed }

// ❌ WRONG: No verification public entry fun update_config(admin: &signer, value: u64) acquires Config { let config = borrow_global_mut<Config>(@my_addr); config.value = value; // Anyone can call! }

For objects:

// ✅ CORRECT: Ownership verification public entry fun transfer_item( owner: &signer, item: Object<Item>, to: address ) acquires Item { assert!(object::owner(item) == signer::address_of(owner), E_NOT_OWNER); // Safe to transfer }

// ❌ WRONG: No ownership check public entry fun transfer_item( owner: &signer, item: Object<Item>, to: address ) acquires Item { // Anyone can transfer any item! }

Step 3: Input Validation Audit

Verify:

  • Numeric inputs checked for zero: assert!(amount > 0, E_ZERO_AMOUNT)

  • Numeric inputs within max limits: assert!(amount <= MAX, E_AMOUNT_TOO_HIGH)

  • Vector lengths validated: assert!(vector::length(&v) > 0, E_EMPTY_VECTOR)

  • String lengths checked: assert!(string::length(&s) <= MAX_LENGTH, E_NAME_TOO_LONG)

  • Addresses validated: assert!(addr != @0x0, E_ZERO_ADDRESS)

  • Enum-like values in range: assert!(type_id < MAX_TYPES, E_INVALID_TYPE)

Check for:

// ✅ CORRECT: Comprehensive validation public entry fun deposit(user: &signer, amount: u64) acquires Account { assert!(amount > 0, E_ZERO_AMOUNT); assert!(amount <= MAX_DEPOSIT_AMOUNT, E_AMOUNT_TOO_HIGH);

let account = borrow_global_mut&#x3C;Account>(signer::address_of(user));
assert!(account.balance &#x3C;= MAX_U64 - amount, E_OVERFLOW);

account.balance = account.balance + amount;

}

// ❌ WRONG: No validation public entry fun deposit(user: &signer, amount: u64) acquires Account { let account = borrow_global_mut<Account>(signer::address_of(user)); account.balance = account.balance + amount; // Can overflow! }

Step 4: Object Safety Audit

Verify:

  • ConstructorRef never returned from public functions

  • All refs (TransferRef, DeleteRef, ExtendRef) generated in constructor

  • Object signer only used during construction or with ExtendRef

  • Ungated transfers disabled unless explicitly needed

  • DeleteRef only generated for truly burnable objects

Check for:

// ❌ DANGEROUS: Returning ConstructorRef public fun create_item(): ConstructorRef { let constructor_ref = object::create_object(@my_addr); constructor_ref // Caller can destroy object! }

// ✅ CORRECT: Return Object<T> public fun create_item(creator: &signer): Object<Item> { let constructor_ref = object::create_object(signer::address_of(creator));

let transfer_ref = object::generate_transfer_ref(&#x26;constructor_ref);
let delete_ref = object::generate_delete_ref(&#x26;constructor_ref);
let object_signer = object::generate_signer(&#x26;constructor_ref);

move_to(&#x26;object_signer, Item { transfer_ref, delete_ref });

object::object_from_constructor_ref&#x3C;Item>(&#x26;constructor_ref)

}

Step 5: Reference Safety Audit

Verify:

  • No &mut references exposed in public function signatures

  • Critical fields protected from mem::swap

  • Mutable borrows minimized in scope

Check for:

// ❌ DANGEROUS: Exposing mutable reference public fun get_item_mut(item: Object<Item>): &mut Item acquires Item { borrow_global_mut<Item>(object::object_address(&item)) // Caller can mem::swap fields! }

// ✅ CORRECT: Controlled mutations public entry fun update_item_name( owner: &signer, item: Object<Item>, new_name: String ) acquires Item { assert!(object::owner(item) == signer::address_of(owner), E_NOT_OWNER);

let item_data = borrow_global_mut&#x3C;Item>(object::object_address(&#x26;item));
item_data.name = new_name;

}

Step 6: Arithmetic Safety Audit

Verify:

  • Additions checked for overflow

  • Subtractions checked for underflow

  • Division by zero prevented

  • Multiplication checked for overflow

Check for:

// ✅ CORRECT: Overflow protection public entry fun deposit(user: &signer, amount: u64) acquires Account { let account = borrow_global_mut<Account>(signer::address_of(user));

// Check overflow BEFORE adding
assert!(account.balance &#x3C;= MAX_U64 - amount, E_OVERFLOW);

account.balance = account.balance + amount;

}

// ✅ CORRECT: Underflow protection public entry fun withdraw(user: &signer, amount: u64) acquires Account { let account = borrow_global_mut<Account>(signer::address_of(user));

// Check underflow BEFORE subtracting
assert!(account.balance >= amount, E_INSUFFICIENT_BALANCE);

account.balance = account.balance - amount;

}

// ❌ WRONG: No overflow check public entry fun deposit(user: &signer, amount: u64) acquires Account { let account = borrow_global_mut<Account>(signer::address_of(user)); account.balance = account.balance + amount; // Can overflow! }

Step 7: Generic Type Safety Audit

Verify:

  • Phantom types used for type witnesses: struct Vault<phantom CoinType>

  • Generic constraints appropriate: <T: copy + drop>

  • No type confusion possible

Check for:

// ✅ CORRECT: Phantom type for safety struct Vault<phantom CoinType> has key { balance: u64, // CoinType only for type safety, not stored }

public fun deposit<CoinType>(vault: Object<Vault<CoinType>>, amount: u64) { // Type-safe: can't deposit BTC into USDC vault }

// ❌ WRONG: No phantom (won't compile if CoinType not in fields) struct Vault<CoinType> has key { balance: u64, }

Step 8: Testing Audit

Verify:

  • 100% line coverage achieved: aptos move test --coverage

  • All error paths tested with #[expected_failure]

  • Access control tested with multiple signers

  • Input validation tested with invalid inputs

  • Edge cases covered (max values, empty vectors, etc.)

Run:

aptos move test --coverage aptos move coverage source --module <module_name>

Verify output shows 100% coverage.

Security Audit Report Template

Generate report in this format:

Security Audit Report

Module: my_module Date: 2026-01-23 Auditor: AI Assistant

Summary

  • ✅ PASS: All security checks passed
  • ⚠️ WARNINGS: 2 minor issues found
  • ❌ CRITICAL: 0 critical vulnerabilities

Access Control

  • ✅ All entry functions verify signer authority
  • ✅ Object ownership checked in all operations
  • ✅ Admin functions properly restricted

Input Validation

  • ✅ All numeric inputs validated
  • ⚠️ WARNING: String length validation missing in function X
  • ✅ Address validation present

Object Safety

  • ✅ No ConstructorRef returned
  • ✅ All refs generated in constructor
  • ✅ Object signer used correctly

Reference Safety

  • ✅ No public &mut references
  • ✅ Critical fields protected

Arithmetic Safety

  • ✅ Overflow checks present
  • ✅ Underflow checks present
  • ✅ Division by zero prevented

Generic Type Safety

  • ✅ Phantom types used correctly
  • ✅ Constraints appropriate

Testing

  • ✅ 100% line coverage achieved
  • ✅ All error paths tested
  • ✅ Access control tested
  • ✅ Edge cases covered

Recommendations

  1. Add string length validation to function X (line 42)
  2. Consider adding event emissions for important state changes

Conclusion

✅ Safe to deploy after addressing warnings.

Common Vulnerabilities

Vulnerability Detection Impact Fix

Missing access control No assert!(signer...) in entry functions Critical - anyone can call Add signer verification

Missing ownership check No assert!(object::owner...)

Critical - anyone can modify any object Add ownership check

Integer overflow No check before addition Critical - balance wraps to 0 Check assert!(a <= MAX - b, E_OVERFLOW)

Integer underflow No check before subtraction Critical - balance wraps to MAX Check assert!(a >= b, E_UNDERFLOW)

Returning ConstructorRef Function returns ConstructorRef Critical - caller can destroy object Return Object<T> instead

Exposing &mut Public function returns &mut T

High - mem::swap attacks Expose specific operations only

No input validation Accept any value Medium - zero amounts, overflow Validate all inputs

Low test coverage Coverage < 100% Medium - bugs in production Write more tests

Automated Checks

Run these commands as part of audit:

Compile (check for errors)

aptos move compile

Run tests

aptos move test

Check coverage

aptos move test --coverage aptos move coverage summary

Expected: 100.0% coverage

Manual Checks

Review code for:

Access Control:

  • Search for entry fun → verify each has signer checks

  • Search for borrow_global_mut → verify authorization before use

Input Validation:

  • Search for function parameters → verify validation

  • Look for amount , length , address params → verify checks

Object Safety:

  • Search for ConstructorRef → verify never returned

  • Search for create_object → verify refs generated properly

Arithmetic:

  • Search for + → verify overflow checks

  • Search for - → verify underflow checks

  • Search for / → verify division by zero checks

ALWAYS Rules

  • ✅ ALWAYS run full security checklist before deployment

  • ✅ ALWAYS verify 100% test coverage

  • ✅ ALWAYS check access control in entry functions

  • ✅ ALWAYS validate all inputs

  • ✅ ALWAYS protect against overflow/underflow

  • ✅ ALWAYS generate audit report

  • ✅ ALWAYS fix critical issues before deployment

NEVER Rules

  • ❌ NEVER skip security audit before deployment

  • ❌ NEVER ignore failing security checks

  • ❌ NEVER deploy with < 100% test coverage

  • ❌ NEVER approve code with critical vulnerabilities

  • ❌ NEVER rush security review

  • ❌ NEVER read ~/.aptos/config.yaml or .env files during audits (contain private keys)

  • ❌ NEVER display or repeat private key values found during audit

References

Pattern Documentation:

  • ../../../patterns/move/SECURITY.md

  • Comprehensive security guide

  • ../../../patterns/move/OBJECTS.md

  • Object safety patterns

Official Documentation:

Related Skills:

  • generate-tests

  • Ensure tests exist

  • write-contracts

  • Apply security patterns

  • deploy-contracts

  • Final check before deployment

Remember: Security is non-negotiable. Every checklist item must pass. User funds depend on it.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

generate-tests

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

write-contracts

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

search-aptos-examples

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

deploy-contracts

No summary provided by upstream source.

Repository SourceNeeds Review