agents/skills/injectable/integration-hazard-research/SKILL.md
Protocol Type Trigger NAMED_EXTERNAL_PROTOCOL (detected when recon finds import/interface for an identifiable external protocol — not standard libraries). Researches known integration hazards of the target protocol.
npx skillsauth add plamentsv/plamen integration-hazard-researchInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Protocol Type Trigger:
NAMED_EXTERNAL_PROTOCOL— detected when recon identifies imports or interface calls to a named external protocol (Uniswap, Aave, Balancer, Compound, Curve, Chainlink, Lido, MakerDAO, etc.) that is NOT a standard library (OpenZeppelin, solmate, solady) and NOT the protocol under audit itself. Inject Into: depth-external agent Language: All chains (EVM primary; Solana/Aptos/Sui when integrating with named on-chain protocols) Finding prefix:[IHR-N]Added in: v1.1.5
This skill adds a research phase (Section 0) before depth-external's existing code analysis. All sections map to depth-external's domain. The orchestrator includes this skill in the depth-external agent's prompt when NAMED_EXTERNAL_PROTOCOL is flagged.
When decomposing into investigation questions:
Recon Agent 3 detects named external protocol imports during TASK 6 pattern scanning. Indicators:
IUniswapV2Router, IUniswapV3Pool, IBalancerVault, IPool, IAToken, ICToken, ILendingPool, ICurvePool, IChainlinkAggregator, IStETH@uniswap/, @aave/, @balancer-labs/, @chainlink/, @openzeppelin/ (only when calling protocol-specific functions, not generic utilities)The flag records which protocols were detected: NAMED_EXTERNAL_PROTOCOL: [Uniswap V3, Chainlink]
For each section below, execute in order:
For EACH named external protocol detected by recon:
DO NOT call mcp__unified-vuln-db__search_solodit_live or any tavily/web-search
MCP tool for dependency research. They are unavailable in depth-phase subagent
contexts — the driver launches depth workers with --disallowedTools mcp__*
and an empty MCP server config to prevent cold-start hangs. Any Solodit/Tavily
MCP call in this phase will silently fail or hang; do not attempt it.
Read {SCRATCHPAD}/external_dependency_research.md instead. This is the
recon-baked research ledger: recon runs as a phase-LLM with live
WebSearch/WebFetch/tavily_search access and already researched every
detected external dependency's real interface/semantics (deployed source,
ABI/arity, monotonicity, gas/error behavior) before depth ever runs. For each
target protocol/dependency, find its row: Dependency | Integration Surface (file:line) | Assumed Behavior (as coded) | Real Behavior (researched) | Source (URL + fetch date) | Conformance MATCH/MISMATCH/CHECK | Fetch Status OK/FETCH_FAILED:reason. Use the ledger's Real Behavior / Conformance columns
as your hazard-catalog input for Section 0c below instead of live search
results.
For an integration surface in your target that is NOT covered by any ledger
row (a dependency the ledger missed, or a row with Fetch Status: FETCH_FAILED), do NOT guess the real behavior and do NOT silently fall
through to the 0d floor catalog as if it were live research. Emit, in your
finding output, one escalation line per uncovered surface:
NEEDS_DEPENDENCY_RESEARCH: <dependency>:<file:line>: <what you need to know>
Then proceed under the assumed WORST-CASE realistic external condition per
Rule 10 (rules/finding-output-format.md), tagging the finding
[EXTERNAL-ASSUMPTION: <assumed condition>]. Note: native WebSearch/
WebFetch (non-MCP Claude Code tools) remain available if you need a single
targeted check beyond the ledger — but the ledger is the primary source and
should cover the large majority of surfaces; ad-hoc web search is not a
substitute for reading it first.
| Target Protocol | Known Integration Hazard | Severity | Root Cause | Source | Applicable to This Integration? | |----------------|------------------------|----------|------------|--------|-------------------------------| | {protocol} | {hazard title} | {sev} | {brief root cause} | {ledger row dependency name / URL from external_dependency_research.md} | YES / NO / CHECK |
Applicability criteria (same as FORK_ANCESTRY):
Cross-chain gateways frequently deliver the gas token in a DIFFERENT FORM than the handler assumes — native vs wrapped (e.g. ETH vs WETH). This is an asset-FORM mismatch, distinct from the asset-IDENTITY (declared-output-token vs delivered-token) mismatch. For EACH inbound callback (onCall/onReceive/onRevert/router callback) and EACH outbound deposit/withdraw:
approve/transferFrom/swap requires the asset to already be wrapped; a .call{value:}/native send requires it to be native.UNVERIFIED): the callback names a wrapped-token address as the asset param but the handler moves it via ERC20 approve/transfer with NO deposit{value:}/withdraw() reconciliation (or vice-versa). The missing inbound native→wrapped (or wrapped→native) conversion is a candidate asset-form mismatch — verify reachability and impact.UNVERIFIED.This floor is a coarse last resort, not a substitute for 0a. Use it ONLY when
BOTH hold: (1) external_dependency_research.md has no row for the
dependency (or the row is Fetch Status: FETCH_FAILED), AND (2) you have
already emitted the matching NEEDS_DEPENDENCY_RESEARCH escalation line for
it per 0b. Do NOT reach for this table as a first move, and do NOT treat a
hit here as equivalent to a live-researched ledger row — it only lists
historical bug classes for a fixed list of famous protocols, not the
dependency's current real interface/semantics. Check EACH applicable
protocol against this minimum catalog:
This floor is keyed on the dependency's TYPE (generic mechanism), NOT on any specific protocol name — brand-keyed rows are prohibited (a floor row naming a specific protocol is the confirmed benchmark-contamination vector; see the HARD no-overfit rule). Classify the detected dependency into a type below and check the generic hazard class; use at most one illustrative brand only in prose, never as the row key.
| Dependency Type | Generic Integration Hazard (class) | Check For |
|-----------------|-----------------------------------|-----------|
| AMM / swap pool | Slippage bypass when min-out may be 0; stale TWAP/observation read from an inactive pool; read-only reentrancy via pool-balance views during a callback | any swap call with zero/unbounded slippage; a price/observation read with no freshness assertion; a balance query during or after the external interaction |
| Lending / money market | First-depositor / empty-market exchange-rate rounding; flash-borrow-driven oracle shift within one tx; governance-mutable collateral factor assumed constant | receipt-token interaction at low total supply; an oracle read after interacting with the dependency; a hardcoded LTV / collateral factor |
| Oracle / price feed | Stale price (publisher or L2-sequencer downtime) consumed without a max-staleness check; confidence / exponent field ignored; feed or aggregator address assumed permanent | a price read with no freshness/staleness bound; use of the price without checking its confidence/exponent; a hardcoded feed address |
| Bridge / cross-chain messenger | Delivered value in a different FORM than the handler assumes (native vs wrapped auto-(un)wrap); message/return arity or ABI differing from the vendored interface; zero- or underfunded-gas send that "succeeds" without delivery | the inbound native↔wrapped conversion is present and the first value-moving op's form matches the delivered form; the real deployed message signature vs the vendored trait; the messenger's zero-gas success semantics |
| Staking / rebasing token | Balance changing between blocks with no transfer (rebase) used as an accounting input; share↔underlying conversion rate assumed constant | a balanceOf used as accounting that can rebase; a hardcoded or cached conversion rate |
| Delayed-claim / epoch ticket | State claimable only after an epoch/time boundary read as if immediately available | ticket/order state reads across an epoch or time boundary |
| Concentrated-liquidity / order-book DEX | Tick-density gas-exhaustion DoS on swap; permissionless cancellation of an order a third party can race | swap calls into narrow-tick pools; reading order/position state a third party can cancel or mutate |
Note: This floor lists generic hazard CLASSES by dependency type only — it is minimum coverage, not exhaustive, and NOT a substitute for the live-researched ledger (0a). Real research typically surfaces several more hazards specific to the actual dependency.
Write the hazard catalog to {SCRATCHPAD}/integration_hazard_catalog.md. This file is consumed by:
For each YES/CHECK hazard in the catalog, ask:
Permissionless function race: Does the target protocol expose permissionless functions that modify state the audited code depends on? Can a third party call that function between the audited code's transactions?
| Target Function | Permissionless? | State Modified | Our Code Reads This State? | Race Window | |----------------|----------------|----------------|---------------------------|-------------| | {function} | YES/NO | {state var} | {where our code reads it} | {blocks/time} |
For each race with Permissionless=YES AND Our Code Reads=YES:
Tag: [TRACE:third_party_call({target_function}) → our_read({our_function}) → state_is={stale/zeroed/modified} → impact={loss/revert/incorrect_accounting}]
For each external state the audited code reads:
| State Read | Read Location | Used At | Time Between Read and Use | Can State Change In Window? | |-----------|---------------|---------|---------------------------|---------------------------| | {value} | {our function:line} | {downstream use:line} | {same tx / cross-tx / cross-block} | YES/NO |
For each Can Change=YES:
Tag: [VARIATION:external_state({value}) fresh_at_read=X → stale_at_use=Y → delta={amount} → impact={consequence}]
Coverage assertion: Before returning, verify every entity enumerated under each section has been processed. Report enumerated vs analyzed counts in your return message.
| Section | Required | Completed? | Notes |
|---------|----------|------------|-------|
| 0a. Ledger read (external_dependency_research.md) per target dependency | YES — no MCP calls | Y/N/? | |
| 0b. NEEDS_DEPENDENCY_RESEARCH emitted for every ledger-uncovered surface | YES for each uncovered surface | Y/N/? | |
| 0c. Hazard catalog compiled | YES | Y/N/? | |
| 0d. Floor catalog checked (TERTIARY — ledger-miss AND 0b escalation only) | IF 0a had no row AND 0b escalated | Y/N/? | |
| 0e. integration_hazard_catalog.md written | YES | Y/N/? | |
| 1. Third-party race conditions | FOR EACH YES/CHECK hazard | Y/N/? | |
| 2. Integration state TOCTOU | FOR EACH external state read | Y/N/? | |
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger Pattern Always (run during recon TASK 0, not breadth) - Inject Into Recon agent only (meta_buffer.md enrichment)
data-ai
Trigger STABLESWAP_FORK flag (fork-ancestry detects Curve/StableSwap parent via get_d/get_y/ramp_a/StableSwap patterns) - Agent Type general-purpose (standalone niche agent, 1 budget slot)