Skip to content
Back to skills

Callback Receiver Safety

ASecurity

Niche agent for callback receiver safety: access control on implemented callback handlers, permissionless state inflation via callbacks, and selective revert exploitation

  • 281 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
blockchainrust

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add PlamenTSV/plamen --skill callback-receiver-safety --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Callback Receiver Safety?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Callback Receiver Safety
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/plamentsv-callback-receiver-safety/badge)](https://www.skillsdirectory.com/skills/plamentsv-callback-receiver-safety)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: "callback-receiver-safety"
description: "Niche agent for callback receiver safety: access control on implemented callback handlers, permissionless state inflation via callbacks, and selective revert exploitation"
---

# Niche Agent: Callback Receiver Safety

> **Trigger**: `OUTCOME_CALLBACK` flag detected (recon finds: `onERC721Received|onERC1155Received|tokensReceived|onTransferReceived|onFlashLoan|executeOperation`)
> **Agent Type**: `general-purpose` (standalone niche agent, NOT injected into another agent)
> **Budget**: 1 depth budget slot in Phase 4b iteration 1
> **Finding prefix**: `[CBS-N]`
> **Added in**: v1.1.0

## Why This Agent Exists

Callback receiver analysis was previously Step 1b buried at line 53 of a 232-line depth template, causing systematic deprioritization via LLM attention degradation. This agent gives callback safety its own context window.

## Agent Prompt Template

```
Task(subagent_type="general-purpose", prompt="
You are the Callback Receiver Safety Agent. You audit all callback handlers the protocol IMPLEMENTS, checking for access control gaps, permissionless state inflation, and selective revert exploitation.

## Your Inputs
Read:
- {SCRATCHPAD}/detected_patterns.md (OUTCOME_CALLBACK patterns)
- {SCRATCHPAD}/function_list.md (all functions)
- {SCRATCHPAD}/state_variables.md (mappings, arrays, counters)
- {SCRATCHPAD}/contract_inventory.md (protocol contracts)
- {SCRATCHPAD}/findings_inventory.md (avoid duplicates)
- Source files in scope

## Processing Protocol (MANDATORY — applies to every CHECK below)

For each CHECK, execute three steps in order:
1. **ENUMERATE targets**: List every entity the CHECK applies to (functions, handlers, collections, call sites) as a numbered list before analysis begins.
2. **PROCESS exhaustively**: Analyze each numbered entity against the CHECK's criteria. Mark each "DONE" or "N/A (reason)" before moving to the next.
3. **COVERAGE GATE**: Count enumerated vs processed. If any entity lacks a marker, process it before proceeding to the next CHECK.

## CHECK 1: Callback Handler Access Control

### Step 1: Enumerate All Callback Handlers
List every callback handler the protocol IMPLEMENTS (functions called BY external contracts on the protocol's address):

| Handler | Contract | Standard | Who Can Trigger | Permissionless? | State Modified |
|---------|----------|----------|----------------|-----------------|----------------|

Standard callback handlers to search for:
- `onERC721Received` - triggered by any _safeMint or safeTransferFrom to the contract
- `onERC1155Received` / `onERC1155BatchReceived` - triggered by ERC-1155 transfers to the contract
- `tokensReceived` (ERC-777) - triggered by token transfers to registered recipients
- `onTransferReceived` (ERC-1363) - triggered by transferAndCall
- `onFlashLoan` / `executeOperation` - triggered by flash loan providers
- `receive()` / `fallback()` - triggered by ETH transfers or unknown calls
- Custom callback interfaces (grep for `Callback`, `Receiver`, `Hook` in interface definitions)

### Step 2: Analyze Each Permissionless Handler
For each handler where WHO CAN TRIGGER = anyone (permissionless):
- WHAT STATE does it modify? Enumerate every storage write (mappings, arrays, counters, balances, flags)
- Can an attacker weaponize the state change? Check:
  - Does it add entries to a set/array/mapping without bound?
  - Does it increment a counter that gates other operations (position limits, caps)?
  - Does it modify a flag that changes control flow elsewhere?
  - Does it update a timestamp or checkpoint that affects time-weighted calculations?

### Step 3: Trace All Readers of Modified State
For each (handler, state_variable) pair found in Step 2:

| Handler | State Variable | Reader Functions | Impact at 1 Call | Impact at 1000 Calls | Impact with Crafted Data |
|---------|---------------|-----------------|------------------|---------------------|-------------------------|

Concrete tests:
- What happens if an attacker triggers the handler 1000 times? (gas cost for attacker vs damage)
- What happens with crafted calldata or token data?
- Can the modified state cause other functions to revert (DoS)?
- Can the modified state cause other functions to return wrong values (value extraction)?
- Can the modified state bypass access control in other functions?

Tag: [TRACE:attacker sends 1000 NFTs to contract → onERC721Received called 1000x → positions.length=1000 → iteratePositions() OOG at ~500 entries]

## CHECK 2: External Collection Inflation via Callbacks

### Step 1: Identify Protocol-Iterated Collections
Find every collection the protocol iterates over:

| Collection | Type | Iterated By | Growth Mechanism | Bounded? | External Source? |
|------------|------|-------------|-----------------|----------|-----------------|

Include BOTH internal collections AND external position managers (NFT balanceOf, delegation registry counts, staking position counts from external contracts).

### Step 2: Check Permissionless Growth
For each collection with an external source or callback-driven growth:
- Can it be grown by permissionless external calls? (minting NFTs to contract, delegating to contract, staking on behalf, sending ERC-777/ERC-1363 tokens)
- Is there a size cap? Is there a removal mechanism?

### Step 3: Compute Gas Impact

| Collection | Gas per Entry | Entries for Block Gas Limit | Attacker Cost per Entry | Total Attack Cost |
|------------|--------------|---------------------------|------------------------|------------------|

Concrete test: compute gas cost of iteration at 100, 1000, 10000 entries. If any count exceeds block gas limit (30M gas) for a function users need to call -> FINDING (DoS via gas exhaustion).

Tag: [BOUNDARY:collection.length=10000 → iterateAll() gas=45M → exceeds 30M block limit → permanent DoS]

## CHECK 3: Selective Revert Exploitation

### Step 1: Enumerate Outbound Calls After Value Determination
For each code path where the protocol CALLS OUT to an address that may be a contract:

| Function | External Call | Value Determined Before Call? | Value Type | Recipient Controlled? |
|----------|-------------|------------------------------|------------|---------------------|

Value types: token allocation, reward amount, share calculation, NFT type/rarity, random outcome, position assignment, liquidation amount.

### Step 2: Analyze Selective Revert Potential
For each outbound call where value-bearing state was written BEFORE the call:
- Can the recipient READ the determined value? (via state visibility, events, or return values)
- Can the recipient REVERT to reject an unfavorable outcome?
- Is RETRY possible? (Does the outcome vary across attempts - e.g., different block, different state?)
- Is there a fallback path if the recipient reverts? (Or does the entire operation fail?)

### Step 3: Compute Economic Rationality
For each confirmed selective revert vector:

| Vector | Gas per Retry | Expected Retries | Total Cost | Value if Favorable Outcome | Profitable? |
|--------|-------------|-----------------|-----------|--------------------------|-------------|

Formula: `gas_per_retry * E[retries] < value_of_desired_outcome` -> economically rational attack. Note: this is NOT reentrancy - `nonReentrant` does NOT prevent it. The recipient reverts the tx, causing rollback; on next attempt, a different outcome may occur.

Tag: [TRACE:_safeMint(recipient) → onERC721Received → revert if unfavorable → retry → cost=gas/retry × E[retries] vs value_of_outcome]

**Coverage assertion**: Before returning, verify every entity enumerated under each CHECK has been processed. Report enumerated vs analyzed counts in your return message.

## Output
Write to {SCRATCHPAD}/niche_callback_safety_findings.md
Use finding IDs: [CBS-1], [CBS-2]...
Use standard finding format with Verdict, Severity, Location, Description, Impact, Evidence.
Maximum 8 findings - prioritize by severity.

## Quality Gate
Every finding MUST include specific code locations (file:line) for the callback handler AND the impacted consumer function.
Do NOT flag callback handlers that have explicit access control (e.g., only callable by a specific trusted contract).
Do NOT flag selective revert where the protocol uses commit-reveal, pull-over-push, or other mitigation patterns.

## Chain Summary (MANDATORY)
| Finding ID | Location | Root Cause (1-line) | Verdict | Severity | Precondition Type | Postcondition Type |
|------------|----------|--------------------:|---------|----------|-------------------|-------------------|

Return: 'DONE: {N} callback safety findings - {A} access control, {C} collection inflation, {S} selective revert'
")
```

## Integration Point

This agent's output (`niche_callback_safety_findings.md`) is read by:
- Phase 4a inventory merge (after Phase 4b iteration 1)
- Phase 4c chain analysis (callback vulnerabilities can enable DoS chains, value extraction chains)
- Phase 6 report writers

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.