/reentrancy-analysis
Trigger REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) - Used by Breadth agents, depth-state-trace
$ npx -y skills add PlamenTSV/plamen --skill reentrancy-analysis --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
/reentrancy-analysis
Context preview
The summary Claude sees to decide when to auto-load this skill.
Trigger REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) - Used by Breadth agents, depth-state-trace
SKILL.md
reentrancy-analysis.SKILL.mdname: "reentrancy-analysis"
description: "Trigger REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) - Used by Breadth agents, depth-state-trace"
Skill: REENTRANCY_ANALYSIS
> **Trigger**: REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) > **Used by**: Breadth agents, depth-state-trace > **Covers**: Cross-module reentrancy via closures, dispatchable FA hook reentrancy, direct/indirect reentrancy, resource lock gaps
Purpose
Audit reentrancy vectors in Aptos Move. Historically, Move's linear type system and static dispatch prevented reentrancy. Post Move 2.2, function values (closures) and dispatchable FungibleAsset hooks introduce dynamic dispatch, creating reentrancy surfaces analogous to EVM callbacks but with different mechanics and mitigations.
Background: Aptos Reentrancy Model
**Pre Move 2.2**: No dynamic dispatch. All function calls are statically resolved at compile time. Reentrancy was architecturally impossible (no callbacks, no external calls to untrusted code).
**Post Move 2.2**: Two reentrancy vectors exist: 1. **Function values / closures**: `|arg| { body }` syntax allows passing executable code as parameters. A module calling a user-supplied closure can be reentered. 2. **Dispatchable FungibleAsset hooks**: `withdraw`, `deposit`, and `derived_balance` hooks execute external module code during FA operations. This is framework-level dynamic dispatch.
**`#[module_lock]`**: Prevents INDIRECT reentrancy (cross-module reentry into the locked module). Does NOT prevent DIRECT reentrancy (closure calling back into the same module's function within the same execution frame).
Methodology
STEP 1: Dynamic Dispatch Point Inventory
Find ALL uses of dynamic dispatch in the audited modules:
1a. Function Values and Closures
**MANDATORY SEARCH**: Grep all `.move` files for: 1. `|` followed by parameter patterns (closure syntax: `|x| { ... }`, `|x, y| { ... }`) 2. Function types in signatures (e.g., `callback: |u64| -> u64`, `FunctionValue`) 3. `move |` (move closures that capture variables) 4. Functions that accept function-typed parameters
| # | Module | Function | Dynamic Dispatch Type | Caller-Controlled? | Reentrancy Risk | |---|--------|----------|----------------------|-------------------|----------------| | 1 | {module} | {func} | Closure parameter | YES/NO | {assess} | | 2 | {module} | {func} | Stored function value | YES/NO | {assess} |
1b. Dispatchable FA Hooks
**MANDATORY SEARCH**: Grep for: 1. `dispatchable_fungible_asset` module usage 2. `register_dispatch_functions` or equivalent hook registration 3. `withdraw_with_*`, `deposit_with_*` function patterns 4. `derived_balance` implementations
| # | Module | Hook Type | Registered Function | External Code Executed? | |---|--------|-----------|--------------------|-----------------------| | 1 | {module} | withdraw | {module::withdraw_hook} | YES - at every withdrawal | | 2 | {module} | deposit | {module::deposit_hook} | YES - at every deposit | | 3 | {module} | derived_balance | {module::balance_hook} | YES - at every balance query |
STEP 2: Module Lock Analysis
For each module containing dynamic dispatch points:
| Module | Has `#[module_lock]`? | Public Entry Points | Protected by Lock? | Direct Reentry Possible? | |--------|---------------------|--------------------|--------------------|------------------------| | {module} | YES/NO | {list entry/public functions} | YES/NO | {YES if lock present - lock prevents indirect but not direct} |
**CRITICAL DISTINCTION**:
- `#[module_lock]` = YES: **Indirect** reentrancy blocked (Module A -> closure -> Module A's function). **Direct** reentrancy still possible (within same function frame, closure calls same module's public function via friend or inline).
- `#[module_lock]` = NO: Both direct and indirect reentrancy possible.
**Check**: For each module WITHOUT `#[module_lock]`: 1. Does it have any dynamic dispatch points (from Step 1)? 2. If YES: cross-module reentrancy is possible - trace all paths.
STEP 3: Third-Party Resource Lock Bypass
If the audited module stores data in a third-party resource abstraction:
| Data Structure | Provided By Module | Our Module Uses | Third-Party Lock Protects Us? | |---------------|-------------------|----------------|------------------------------| | SmartTable | aptos_std | YES/NO | NO - their lock protects THEIR invariants, not ours | | Table | aptos_std | YES/NO | NO | | {custom_struct} | {third_party} | YES/NO | NO |
**Pattern**: Module A stores its accounting data in a SmartTable (from `aptos_std`). `aptos_std` may have `#[module_lock]`. But this lock only prevents reentry into `aptos_std` - it does NOT prevent reentry into Module A. An attacker can reenter Module A while Module A's SmartTable operation is in progress.
**Check**: Does the protocol rely on a third-party module's lock for its own reentrancy protection? If YES -> FINDING.
STEP 4: State Consistency Analysis (Check-Effect-Interaction)
For each dynamic dispatch point identified in Step 1:
4a. Pre-Dispatch State Snapshot
| Dispatch Point | State READ Before Dispatch | State MODIFIED Before Dispatch | State Modified AFTER Dispatch | |---------------|--------------------------|------------------------------|------------------------------| | {func:line} | {variables/resources read} | {variables/resources written} | {variables/resources written} |
4b. Reentrancy Impact Trace
For each dispatch point where state is modified before dispatch:
1. Function entry: Read state S1 (e.g., user_balance = 100)
2. Modify state: S1 partially updated (e.g., user_balance -= 50, but total_supply not yet updated)
3. Dynamic dispatch: closure/hook executes
4. REENTRY: Attacker calls back into same module
5. Reentrant call reads: S1 (modified) - sees user_balance = 50
6. Reentrant call reads: S2 (NOT yet modified) - sees stale total_supply = 1000 (shou
Read more
name: "reentrancy-analysis" description: "Trigger REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) - Used by Breadth agents, depth-state-trace"
Skill: REENTRANCY_ANALYSIS
> **Trigger**: REENTRANCY flag detected (dynamic dispatch, closures, dispatchable FA, function values) > **Used by**: Breadth agents, depth-state-trace > **Covers**: Cross-module reentrancy via closures, dispatchable FA hook reentrancy, direct/indirect reentrancy, resource lock gaps
Purpose
Audit reentrancy vectors in Aptos Move. Historically, Move's linear type system and static dispatch prevented reentrancy. Post Move 2.2, function values (closures) and dispatchable FungibleAsset hooks introduce dynamic dispatch, creating reentrancy surfaces analogous to EVM callbacks but with different mechanics and mitigations.
Background: Aptos Reentrancy Model
**Pre Move 2.2**: No dynamic dispatch. All function calls are statically resolved at compile time. Reentrancy was architecturally impossible (no callbacks, no external calls to untrusted code).
**Post Move 2.2**: Two reentrancy vectors exist: 1. **Function values / closures**: `|arg| { body }` syntax allows passing executable code as parameters. A module calling a user-supplied closure can be reentered. 2. **Dispatchable FungibleAsset hooks**: `withdraw`, `deposit`, and `derived_balance` hooks execute external module code during FA operations. This is framework-level dynamic dispatch.
**`#[module_lock]`**: Prevents INDIRECT reentrancy (cross-module reentry into the locked module). Does NOT prevent DIRECT reentrancy (closure calling back into the same module's function within the same execution frame).
Methodology
STEP 1: Dynamic Dispatch Point Inventory
Find ALL uses of dynamic dispatch in the audited modules:
1a. Function Values and Closures
**MANDATORY SEARCH**: Grep all `.move` files for: 1. `|` followed by parameter patterns (closure syntax: `|x| { ... }`, `|x, y| { ... }`) 2. Function types in signatures (e.g., `callback: |u64| -> u64`, `FunctionValue`) 3. `move |` (move closures that capture variables) 4. Functions that accept function-typed parameters
| # | Module | Function | Dynamic Dispatch Type | Caller-Controlled? | Reentrancy Risk | |---|--------|----------|----------------------|-------------------|----------------| | 1 | {module} | {func} | Closure parameter | YES/NO | {assess} | | 2 | {module} | {func} | Stored function value | YES/NO | {assess} |
1b. Dispatchable FA Hooks
**MANDATORY SEARCH**: Grep for: 1. `dispatchable_fungible_asset` module usage 2. `register_dispatch_functions` or equivalent hook registration 3. `withdraw_with_*`, `deposit_with_*` function patterns 4. `derived_balance` implementations
| # | Module | Hook Type | Registered Function | External Code Executed? | |---|--------|-----------|--------------------|-----------------------| | 1 | {module} | withdraw | {module::withdraw_hook} | YES - at every withdrawal | | 2 | {module} | deposit | {module::deposit_hook} | YES - at every deposit | | 3 | {module} | derived_balance | {module::balance_hook} | YES - at every balance query |
STEP 2: Module Lock Analysis
For each module containing dynamic dispatch points:
| Module | Has `#[module_lock]`? | Public Entry Points | Protected by Lock? | Direct Reentry Possible? | |--------|---------------------|--------------------|--------------------|------------------------| | {module} | YES/NO | {list entry/public functions} | YES/NO | {YES if lock present - lock prevents indirect but not direct} |
**CRITICAL DISTINCTION**:
- `#[module_lock]` = YES: **Indirect** reentrancy blocked (Module A -> closure -> Module A's function). **Direct** reentrancy still possible (within same function frame, closure calls same module's public function via friend or inline).
- `#[module_lock]` = NO: Both direct and indirect reentrancy possible.
**Check**: For each module WITHOUT `#[module_lock]`: 1. Does it have any dynamic dispatch points (from Step 1)? 2. If YES: cross-module reentrancy is possible - trace all paths.
STEP 3: Third-Party Resource Lock Bypass
If the audited module stores data in a third-party resource abstraction:
| Data Structure | Provided By Module | Our Module Uses | Third-Party Lock Protects Us? | |---------------|-------------------|----------------|------------------------------| | SmartTable | aptos_std | YES/NO | NO - their lock protects THEIR invariants, not ours | | Table | aptos_std | YES/NO | NO | | {custom_struct} | {third_party} | YES/NO | NO |
**Pattern**: Module A stores its accounting data in a SmartTable (from `aptos_std`). `aptos_std` may have `#[module_lock]`. But this lock only prevents reentry into `aptos_std` - it does NOT prevent reentry into Module A. An attacker can reenter Module A while Module A's SmartTable operation is in progress.
**Check**: Does the protocol rely on a third-party module's lock for its own reentrancy protection? If YES -> FINDING.
STEP 4: State Consistency Analysis (Check-Effect-Interaction)
For each dynamic dispatch point identified in Step 1:
4a. Pre-Dispatch State Snapshot
| Dispatch Point | State READ Before Dispatch | State MODIFIED Before Dispatch | State Modified AFTER Dispatch | |---------------|--------------------------|------------------------------|------------------------------| | {func:line} | {variables/resources read} | {variables/resources written} | {variables/resources written} |
4b. Reentrancy Impact Trace
For each dispatch point where state is modified before dispatch:
1. Function entry: Read state S1 (e.g., user_balance = 100) 2. Modify state: S1 partially updated (e.g., user_balance -= 50, but total_supply not yet updated) 3. Dynamic dispatch: closure/hook executes 4. REENTRY: Attacker calls back into same module 5. Reentrant call reads: S1 (modified) - sees user_balance = 50 6. Reentrant call reads: S2 (NOT yet modified) - sees stale total_supply = 1000 (shou
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

