Cantina’s AI Code Analyzer flagged a high severity liveness issue in Provenance’s trigger module. The result was a bug that could cause a panic in EndBlocker.

The Provenance team confirmed the report and shipped a fix in v1.27.1. If you run provenanced, upgrade to v1.27.1 or later.

Executive summary

The trigger module stores event listeners in a single keyspace indexed by an “event prefix” string.

Two internal trigger types use reserved prefixes:

  • block-height
  • block-time

The EndBlocker detection logic assumed that every trigger stored under those prefixes was of the corresponding concrete type, using direct type assertions.

However, transaction event triggers are indexed by a user controlled string, TransactionEvent.Name. Validation did not forbid using the reserved names.

Result: a user could create a transaction event trigger whose name is block-height or block-time. During EndBlocker, the detector would iterate triggers under the reserved prefix and panic on the unsafe type assertion. Because the trigger is stored in state, the panic can repeat and disrupt liveness.

Severity and impact

This issue is high severity because it sits on the consensus path:

  • EndBlocker runs on every node on every block.
  • A panic is not a recoverable error, it crashes the process.
  • The malicious trigger is stored on chain, so every node observes the same state and executes the same failing logic.

In the Cosmos SDK, panics that occur during transaction processing (e.g., in DeliverTx, CheckTx, PrepareProposal, or ProcessProposal) are automatically recovered by the framework. This safeguard prevents a single faulty transaction from stopping the chain. Conversely, panics within consensus-critical paths, such as module BeginBlocker or EndBlocker functions—are not recovered by default. These errors will crash the node process, leading to a chain halt, as all validators deterministically execute the same failing logic and consequently cannot produce or agree on subsequent blocks.

On affected versions, any fee paying account that can submit the trigger creation message could persist a trigger that causes repeated EndBlocker panics. In practice, that can halt block production until operators upgrade.

Affected versions

Observed as affected:

  • provenanced v1.27.0 (mainnet)
  • provenanced v1.27.0-rc1 (testnet)

Fixed in:

  • provenanced v1.27.1

Technical background

The trigger module supports multiple trigger event types. Internally, it uses an “event prefix” string to group listeners in a store.

Conceptually:

  • Block height triggers are stored under block-height
  • Block time triggers are stored under block-time
  • Transaction event triggers are stored under TransactionEvent.Name

That last point is the key: for transaction event triggers, the prefix is user controlled.

Root cause

There are two ingredients.

  1. A shared keyspace indexed by strings, where user controlled strings can collide with internal reserved prefixes.
  2. Unsafe type assertions in EndBlocker, where the code assumes “everything under this prefix must be this event type.”

In the affected code, the block height and block time detection functions cast the generic trigger event interface to a concrete type via direct assertion. When a transaction event trigger is present under the same prefix, the assertion fails and panics.

What exploitation looks like

A user creates a transaction event trigger with a name equal to a reserved prefix, block-height or block-time.

On the next EndBlocker, the chain iterates triggers under that prefix, attempts to treat the trigger event as a block height or block time event, and panics when the type is not what it assumed.

We reproduced this locally and observed an interface conversion panic consistent with the above behavior. We are not publishing step by step reproduction commands in this post.

The fix

Provenance v1.27.1 changed trigger processing in EndBlocker to avoid panics caused by unexpected trigger event types.

Instead of direct type assertions, it now uses checked conversions, and returns “not a match” when the type is not what the detector expects. That converts a consensus crashing path into a safe no op for mismatched types.

The relevant change is effectively:

Before (unsafe):

blockHeightEvent := triggerEvent.(*types.BlockHeightEvent)

After (safe):

blockHeightEvent, ok := triggerEvent.(*types.BlockHeightEvent)
if !ok {
		return false
}


The same pattern was applied across transaction, height, and time detection.

Recommended hardening beyond the patch

The v1.27.1 change removes the panic. That is the immediate priority for liveness.

If you are designing similar systems, there are additional hardening moves worth considering:

  • Namespace internal prefixes so user supplied identifiers cannot collide with system prefixes.
  • Treat all data loaded from store as untrusted, even when it is “your own” module’s store.
  • Avoid panics on the consensus path. Prefer typed decoding, checked conversions, and explicit error handling.
  • Add property tests that attempt to mix trigger types under the same key prefixes and assert the chain never panics.

Why this is a pattern worth scanning for

This bug class shows up when two ideas get combined:

  • Storage indexing uses a user controlled string.
  • Runtime dispatch uses that string to decide which concrete type to cast to.

If you see string keyed namespaces plus direct type assertions in EndBlocker, you have a candidate for a liveness issue.

How our tool found it

The AI Code Analyzer is built to surface audit grade signal, not a pile of speculative warnings. In this case, the scan highlighted a dangerous combination:

  • user controlled identifier used as an indexing prefix
  • consensus critical code using unsafe type assertions based on that prefix

That is the sort of exploit shaped pattern that is easy to miss in manual review, and extremely costly when it lands in EndBlocker.

Disclosure timeline

  • Reported privately to the Provenance team.
  • The team confirmed the issue.
  • A fix shipped in provenanced v1.27.1, described as “safer type conversion of triggers,” and operators were advised to upgrade.

Closing

If you maintain a chain, a bridge, or any system with consensus critical execution, you should assume attackers will look for “panic surfaces.” Unsafe casts, unchecked assumptions about store contents, and user controlled strings used for dispatch are recurring themes.

If you want early access to the AI Code Analyzer, join the waitlist here.

FAQ

No items found. This section will be hidden on the published page.