agents/skills/injectable/l1/cosmos-ibc-security/SKILL.md
L1 trigger - audits IBC / ibc-go cross-chain entry points for ICS-23 / Merkle proof gaps, ordered-channel sequence integrity, escrow burn<->mint synchronization, light-client version downgrade, and timeout/ack handler reentrancy.
npx skillsauth add plamentsv/plamen cosmos-ibc-securityInstall 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.
L1 trigger:
L1_PATTERN=trueANDIBC(ibc-go //ibc// ics23 / IBC channels detected) Inject Into:depth-consensus-invariant,depth-externalLanguage: Go (Cosmos-SDK / ibc-go) Finding prefix:[IBC-N]Status: v0.1
Recon detects an IBC integration: go.mod requires github.com/cosmos/ibc-go/..., the tree has an /ibc/ subtree, an ics23 / 23-commitment / 02-client / 04-channel path, or IBC application modules (OnRecvPacket, OnAcknowledgementPacket, OnTimeoutPacket, IBCModule impls). IBC is the trust boundary where bytes from a foreign chain enter local state via a verified proof — proof-verification gaps and packet-handler bugs are the highest-severity class here (forged value acceptance, fund theft, chain halt). Severity baseline is Medium; proof-bypass / fund-loss / chain-halt classes upgrade to High/Critical per docs/l1-mode/severity-matrix.md.
An IBC entry path is any code reachable from OnRecvPacket, OnAcknowledgementPacket, OnTimeoutPacket, OnChanOpenInit/Try/Ack/Confirm, OnChanCloseInit/Confirm, a relayer-submitted MsgRecvPacket / MsgAcknowledgement / MsgTimeout / MsgUpdateClient, or any keeper method consuming a commitment proof. Everything in those paths consumes attacker-influenceable bytes (the relayer is untrusted; the counterparty chain may be malicious) and MUST verify proofs against the correct path/prefix before acting.
Check: Every consumption of a cross-chain value must verify a full membership (or non-membership) proof against the counterparty's committed root, using the correct commitment path PREFIX and validating the emitter / source is the expected chain/channel. A proof verified against the wrong path, with a missing prefix, or without binding the value to the expected source, lets a forged value pass (ICS-23 / Dragonberry-class).
Methodology:
caller_map.md / callee_map.md / function_summary.md, enumerate every call to VerifyMembership, VerifyNonMembership, VerifyProof, ics23.VerifyMembership, commitment.VerifyMembership, or any keeper method that takes a proof []byte / MerklePath / MerkleProof argument.commitmenttypes.NewMerklePath with the client's stored prefix / GetCommitmentPrefix()), not a bare key or a hardcoded/empty prefix.clientID / connection / channel), so a value committed by a different chain cannot be replayed in.VerifyProof / VerifyMembership call with no full path/prefix construction, with no emitter==expected-chain (clientID/channel) binding, or where membership/non-membership is mismatched to the claim.Tag: [IBC-PROOF-GAP:{call}:{file}:{line}:{missing:prefix|emitter|membership-kind}]
Check: For an ORDERED channel, packet sequences must be processed contiguously and monotonically — sequence n+1 must not be delivered before n, and a delivered sequence must not be re-delivered. For UNORDERED channels, the receipt store must prevent replay. Missing seq-gap / monotonicity handling lets packets be skipped, reordered, or replayed.
Methodology:
channeltypes.ORDERED / UNORDERED) for each in-scope channel/module and how the handler branches on it.nextSequenceRecv (and nextSequenceSend/nextSequenceAck) read/increment. Confirm OnRecvPacket rejects any packet whose sequence != the expected next, and that the counter increments by exactly one. Flag a missing contiguity check or an increment that can skip/gap.SetPacketReceipt / GetPacketReceipt / HasPacketReceipt) blocks replay before any state effect.Tag: [IBC-SEQ:{channel-kind}:{file}:{line}:{gap|replay|nonmonotonic}]
Check: In transfer-style IBC apps, the source chain escrows (or burns) on send and the sink chain mints a voucher on recv; on the return path the voucher is burned and the escrow released. These must be atomic and balanced: burnAmt <= escrowBal, every mint on the sink corresponds to an escrow on the source, and a refund (timeout/failed-ack) returns exactly the escrowed amount. An overburn, a mint without a paired escrow record, or a desync between escrow ledger and module balance leaks or destroys funds.
Methodology:
GetEscrowAddress), MintCoins/BurnCoins on the transfer module, and the per-channel/denom escrow tracking (SetTotalEscrowForDenom / GetTotalEscrowForDenom or equivalent ledger).OnRecvPacket).burnAmt <= escrowBal (no overburn beyond what is tracked).Burn not guarded by <= escrowBal), mint with no paired escrow/proof, double-refund on both timeout and ack, or an escrow-ledger update that can drift from bankKeeper.GetBalance(escrowAddr, denom).Tag: [IBC-ESCROW-DESYNC:{path}:{file}:{line}:{overburn|unpaired-mint|double-refund|ledger-drift}]
Check: Channel/connection handshake and client updates must reject versions, client types, or feature sets below the supported minimum. Silently accepting a mismatched or downgraded version opens a channel under weaker (or attacker-chosen) semantics — e.g. accepting an ORDERED request as UNORDERED, an unsupported app version, or a deprecated/weaker client type.
Methodology:
OnChanOpenInit/Try/Ack/Confirm, locate the version string / feature negotiation (channeltypes.Version, ValidateChannelParams, app Version checks, metadata JSON for middleware like ICS-29 fee, ICA).MsgCreateClient / MsgUpdateClient), confirm the client type is on an allow-list and that a downgrade (replacing a stronger client with a weaker one, or accepting a consensus state with a regressed height/version) is rejected.Tag: [IBC-VERSION-DOWNGRADE:{handshake-step}:{file}:{line}]
Check: OnTimeoutPacket and OnAcknowledgementPacket (and OnRecvPacket) must complete their own state mutations (refund the escrow, clear the commitment, advance bookkeeping) before invoking external callbacks, middleware, or hooks that can re-enter the IBC stack. A handler that calls out (a contract callback, a downstream module hook, a token send to an arbitrary address) before flushing its state can be re-entered and made to double-refund, double-mint, or skip cleanup (Checks-Effects-Interactions violation).
Methodology:
OnTimeoutPacket / OnAcknowledgementPacket / OnRecvPacket (and middleware wrappers like callbacks/ICS-29 fee/ICA), order the operations: state reads, the commitment/receipt clear, the escrow/balance update, and any external interaction (callback into another module, bankKeeper send to a user-controlled address, sub-message dispatch).Tag: [IBC-REENTRANCY:{handler}:{file}:{line}:{cei-violation|double-refund-window}]
MANDATORY — do not bulk-read large source files (context-collapse risk). Drive analysis from the Go SCIP bake graph artifacts first, then open individual symbols on demand:
caller_map.md, callee_map.md, state_write_map.md, and function_summary.md (produced by the Go SCIP bake) to build the IBC entry-point reachability set (packet handlers, handshake callbacks, proof-consuming keepers) and the per-field writer set (escrow ledger, sequence counters, receipts) without reading whole modules.ibc/ or x/<app> tree or a multi-thousand-line file in one go.OnRecvPacket, VerifyMembership, GetEscrowAddress, nextSequenceRecv) and read only the matched span plus a few lines of context.[IBC-N][FUZZ-PASS] (proptest / Go fuzz on the packet handler or proof verifier) > [NON-DET-PASS] (replay differential) > [CODE-TRACE]docs/l1-mode/severity-matrix.mdIllustrative bug CLASSES only — methodology finds these generically, the names are public-incident exemplars, not protocol targets.
nextSequenceRecv, allowing packets to be skipped or reordered. Catch point: Section 2.burnAmt <= escrowBal or double-refunds on both timeout and failed-ack, draining or destroying escrowed funds. Catch point: Section 3.If the SCIP graph artifacts are unavailable or incomplete:
ibc/**/*.go, x/*/ibc_module.go, x/*/keeper/*.go, and any 02-client / 04-channel / 23-commitment paths.func.*OnRecvPacket, func.*OnAcknowledgementPacket, func.*OnTimeoutPacket, func.*OnChanOpen.VerifyMembership, VerifyNonMembership, VerifyProof, MerklePath, GetCommitmentPrefix.nextSequenceRecv, SetPacketReceipt, GetPacketReceipt, channeltypes.ORDERED.GetEscrowAddress, MintCoins, BurnCoins, TotalEscrowForDenom.OnChanOpenInit, channeltypes.Version, ValidateChannelParams, MsgUpdateClient.cosmos-sdk-module-safety (Cosmos-SDK / CometBFT module entry-point authority, signer mismatch, ABCI panic-safety around proof inputs), light-client-proof-verification (general ICS-23 / Merkle proof soundness across all L1 targets).depth-consensus-invariant, depth-externaldocs/l1-mode/severity-matrix.mddata-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)