Finding 2 of 4
Secret Network Chain Halt: A Single Malformed Raw Transaction Triggers a Chain Halt During FinalizeBlock
Severity: Critical (chain halt)
Affected component:
scrtlabs/SecretNetwork at commit edcbccbbbe65cae1bd93d4d9089ff5d98228f704 (mainnet-affected line: v1.24.1);
Cosmos SDK fork scrtlabs/cosmos-sdk@v0.50.14-secret.12;
consensus fork scrtlabs/tendermint@v0.38.23-secret.1;
SGX enclave block-verifier
Fix: SecretNetwork commit 7eec819ef45fb4d08b0604233ca32bb8fa6685e0, "skipping malformed txs in submit_block_signatures", packaged in a SecretNetwork release on 2026-06-10
Disclosure: coordinated; reported 2026-06-02, fix released 2026-06-10
Cosmos SDK ships a safety net for malformed transactions inside the FinalizeBlock tx-decode loop, which by design runs after beginBlock. Secret's x/compute.BeginBlock forwards attacker-controlled raw tx bytes into an SGX enclave that parses them with unwrap(), before the safety net has a chance to fire.
This writeup describes a vulnerability disclosed to Secret Labs by Bluethroat Labs on 2026-06-02 and fixed on 2026-06-10 under coordinated disclosure. All observations reflect the audited revision. No exploitation was observed in the wild.
Separate incident: This consensus-layer chain-halt vulnerability is unrelated to the June 2026 CW20-ICS20/Axelar bridge infinite-mint exploit. The two incidents affected different components and had different root causes.
Executive Summary
- A single Byzantine active-set proposer, at any height where CometBFT's stake-weighted round-robin selects it, can build a proposal whose transaction list contains one byte,
0xff, and gossip it. No Byzantine voting-power threshold is required. One proposer slot is enough. - Tendermint's
Txs.Validatechecks only aggregate encoded size, so0xffis a valid block-data byte string at the consensus layer. Secret ships BaseApp without an app mempool, which routesProcessProposalthrough Cosmos SDK'sNoOpProcessProposal, which unconditionally returnsACCEPT. Honest validators precommit the malformed proposal. - During
FinalizeBlock, BaseApp callsbeginBlock(req)before it iterates and decodesreq.Txs. Secret'sx/compute.BeginBlockreads the raw proposal transaction bytes off the SDK context and hands them to the SGX enclave viaapi.SubmitBlockSignatures, before the SDK tx loop that would have turned malformed bytes intoErrTxDecoderesponses ever runs. - Inside the enclave,
tx_from_bytes(raw_tx)callsTx::parse_from_bytes(raw_tx).unwrap(). On0xff, the parser returnsWireError(UnexpectedEof), theunwrap()panics, the ECALL returnsSGX_ERROR_ENCLAVE_CRASHED, and Go surfacesCONSENSUS FAILURE!!! err="failed to apply block; error SGX_ERROR_ENCLAVE_CRASHED". The malformed height reaches the consensus commit boundary and finalization fails. - Reproduced end-to-end on real Intel SGX silicon on Azure
Standard_DC8s_v3ineastuswith Azure DCAP-backed remote attestation. Honest and malicious runtime images share byte-identical enclave and SGX-bridge artifacts; only/usr/bin/secretddiffers. All four RPC endpoints stayed pinned atH = 10with block hashD275E21A6EE100632E03424914F69086E40E8F91A56CEEB24A0D222FD235DE32for the full 300-second capture window.
Background
Three composed pieces of shipped software each make one decision that is defensible in isolation. The bug lives in the composition.
Tendermint treats block transactions as opaque bytes. In the Secret consensus fork, Tx is defined as:
// scrtlabs/tendermint@v0.38.23-secret.1/types/tx.go:19
// this is the load-bearing "opaque bytes" contract: Tendermint is the
// consensus layer and by design does not care what the app encodes inside.
// any subsequent "malformed tx" check has to live above this line.
// Tx is arbitrary bytes to be transmitted through the consensus layer.
type Tx []byteTxs.Validate at types/tx.go:107 bounds only the aggregate encoded size. This is correct behavior at the consensus layer: Tendermint is application-agnostic and does not know that some app on top happens to encode cosmos.tx.v1beta1.Tx protobufs inside those bytes. A single-byte raw tx of 0xff is admissible here by design.
Cosmos SDK BaseApp defaults to a NoOp ProcessProposal. When an application does not install a mempool, BaseApp falls back to mempool.NoOpMempool{} at baseapp/baseapp.go:225. ProcessProposalHandler at abci_utils.go:380 then returns NoOpProcessProposal, which at line 426 unconditionally responds ACCEPT. Also defensible: ProcessProposal is a chain-specific validation hook, and the SDK cannot know what any given chain wants to validate at proposal time.
Secret's SGX block verifier needs raw Tendermint tx bytes. The enclave binds validator-set evidence and inner-message witnesses to actual block contents, so x/compute reads the raw proposal txs off the SDK context inside BeginBlock and pushes them across the ECALL. Also defensible: the enclave is the source of truth for the next height's validator-set evidence, and that binding must run against block contents.
Individually, each choice is a reasonable engineering call. The failure is that each one assumes the layer above it has already done work no layer above it has actually done.
Vulnerability Details
Three assumptions, each unfounded
The composition trap looks like this:
| Layer | What it assumes the layer above has enforced | What the layer above actually enforces |
|---|---|---|
| Tendermint | The application will reject anything malformed before it reaches consensus. | Nothing. Tendermint is the consensus layer; nothing sits above it in the block-data path. |
Cosmos SDK NoOpProcessProposal | If the chain cares about proposal contents, it will install a real handler. | Secret does not. app.go:294 constructs BaseApp with no mempool and no ProcessProposal override. |
| SGX block verifier | Every raw tx it receives has already been decoded as a valid Cosmos Tx somewhere. | Nothing has decoded it. x/compute.BeginBlock runs before the SDK tx decode loop. |
Each assumption mirrors the layer above's defensibility. Tendermint is right to be chain-agnostic, which forces the SDK to install a validation policy. The SDK's default policy is right to be permissive, which forces the app to install one. The enclave is right to want raw bytes, which forces someone upstream to validate them. Nobody does.
Layer 1: the SDK's proposal gate is a NoOp
Secret constructs BaseApp without a custom mempool or ProcessProposal handler:
// scrtlabs/SecretNetwork/app/app.go:294 (BaseApp construction)
// no bApp.SetMempool(...), no bApp.SetProcessProposal(...). the app inherits
// the SDK default. the SDK default, when no mempool is installed, is
// NoOpMempool -> NoOpProcessProposal.
bApp := baseapp.NewBaseApp(appName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...)Verified as absence with grep -rn 'SetMempool\|SetProcessProposal' scrtlabs/SecretNetwork/app/ at the audited revision, which returns no results. The dispatch chain, end to end, is: BaseApp's ABCI ProcessProposal entry point dispatches to the bApp.processProposal field. NewBaseApp at baseapp/baseapp.go:225 seeds that field, without an app override, from abci_utils.ProcessProposalHandler(mempool). With Secret's NoOp mempool that call returns NoOpProcessProposal. There is no other layer between Tendermint accepting the proposal and honest validators voting on it.
BaseApp fills the gap unconditionally:
// scrtlabs/cosmos-sdk@v0.50.14-secret.12/baseapp/abci_utils.go:380
// this branch fires whenever no app mempool is installed, which is Secret's config.
func NoOpProcessProposal() sdk.ProcessProposalHandler {
return func(_ sdk.Context, _ *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
// scrtlabs/cosmos-sdk@v0.50.14-secret.12/baseapp/abci_utils.go:426
// ACCEPT is unconditional. no decoding, no parsing, no length check
// beyond what Tendermint already did. any bytes Tendermint accepted,
// this returns yes to.
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
}
}The consensus-time gate that should reject "bytes this chain cannot finalize" is a function that always says yes.
Layer 2: the SDK's safety net runs after the crash
Inside FinalizeBlock, BaseApp runs beginBlock(req) before the tx-decode loop:
// scrtlabs/cosmos-sdk@v0.50.14-secret.12/baseapp/abci.go:782
if err := app.beginBlock(req); err != nil { ... } // BeginBlock runs FIRST
// baseapp/abci.go:811 (illustrative; the actual iterator name is not
// load-bearing -- what matters is that the decode loop is below :782)
for _, rawTx := range req.Txs { // decode loop runs SECOND
// ...
// baseapp/abci.go:817
// this is where ErrTxDecode lives. the graceful path for malformed
// proposal txs. Secret never reaches it because beginBlock crashed
// the enclave for us.
}The SDK has a safety net for malformed transactions. It is in step 2. The bug is that Secret's SGX call is in step 1.
Layer 3: x/compute hands raw bytes to SGX
// scrtlabs/SecretNetwork/x/compute/module.go:187, 220, 228 (BeginBlock)
x2_data := scrt.UnFlatten(ctx.TxBytes()) // raw proposal tx bytes, undecoded
tm_data := tm_type.Data{Txs: x2_data} // wrapped as Tendermint block data
// ...
// api.SubmitBlockSignatures crosses the SGX boundary. whatever the proposer
// sent, the enclave sees. there is no "does every raw tx decode as a Cosmos
// Tx?" check on this host-side path.
random, validator_set_evidence, err = api.SubmitBlockSignatures(
header, b_commit, data, randomAndProof,
)The last host-side moment where the raw bytes could be rejected before the ECALL is right here. There is no rejection.
Layer 4: the enclave's unwrap() on attacker-controlled bytes
Inside the enclave, submit_block_signatures first runs validate_txs, which recomputes the Tendermint DataHash from the same raw bytes:
// scrtlabs/SecretNetwork/cosmwasm/enclaves/shared/block-verifier/src/verify/txs.rs:8
// re-checks Tendermint's own invariant (raw tx bytes hash to header DataHash).
// does NOT enforce Cosmos decodability. re-checks what Tendermint already
// checked; does not check what Tendermint (correctly) declines to check.
pub fn validate_txs(header: &Header, txs: &[Vec<u8>]) -> bool { ... }Then the parse loop:
// scrtlabs/SecretNetwork/cosmwasm/enclaves/shared/block-verifier/src/submit_block_signatures.rs:110-119
// this loop sits BEFORE the #[cfg(feature = "random")] block later in the
// same function. production and verify-validator-whitelist do not gate it.
// any build that reaches ecall_submit_block_signatures (i.e. any build with
// light-client-validation, which mainnet includes) reaches it.
for raw_tx in raw_txs.iter() {
let tx = tx_from_bytes(raw_tx)?;
// ...
}And the panic site itself:
// scrtlabs/SecretNetwork/cosmwasm/enclaves/shared/block-verifier/src/txs.rs:23
pub fn tx_from_bytes(raw_tx: &[u8]) -> SgxResult<Tx> {
// this .unwrap() runs inside the SGX enclave. Tx::parse_from_bytes
// returns Err(WireError(UnexpectedEof)) for 0xff. unwrap() then panics.
// the panic aborts the enclave thread; sgx_status_t bubbles out as
// SGX_ERROR_ENCLAVE_CRASHED, which the Go side translates to a
// consensus panic in FinalizeBlock.
let res = Tx::parse_from_bytes(raw_tx).unwrap();
Ok(res)
}The function's return type is SgxResult<Tx>. The parser's failure mode is representable in that return type. unwrap() discards it. This is the entire finding: one line inside a TEE, on attacker-controlled input, in a function whose signature already had a slot for the error case.
The feature-flag question
Mainnet builds with:
// scrtlabs/SecretNetwork/Makefile:315 (build-mainnet feature list)
// the vulnerable loop at submit_block_signatures.rs:110-119 is reachable
// under this exact flag set. light-client-validation gates the ECALL;
// production and verify-validator-whitelist do not remove or wrap the loop.
verify-validator-whitelist,light-client-validation,production,randomThe panicking parse loop at submit_block_signatures.rs:110-119 sits above the later #[cfg(feature = "random")] block in the same function; production and verify-validator-whitelist do not gate it. light-client-validation is required to enable the ECALL at all in HW mode (see enclaves/shared/ecalls.rs:43-48); without it, the ECALL returns SGX_ERROR_ECALL_NOT_ALLOWED. Mainnet includes it. The mainnet flag set makes the vulnerable code path reachable, not safer.
Attack Scenario
H-1 honest proposer normal block, enclave finalizes normally
H Byzantine proposer proposal.txs = [0xff]; header.DataHash matches;
EncryptedRandom is non-nil
Tendermint layer opaque bytes; aggregate-size check passes; gossips proposal
honest validators BaseApp -> NoOpProcessProposal -> ACCEPT
prevote, precommit, reach consensus commit boundary
FinalizeBlock beginBlock(req) runs BEFORE the SDK tx decode loop
x/compute.BeginBlock ctx.TxBytes() -> api.SubmitBlockSignatures(raw)
enclave validate_txs recomputes DataHash, matches, returns Ok
enclave loop tx_from_bytes(0xff) -> Tx::parse_from_bytes(0xff).unwrap()
panic: WireError(UnexpectedEof)
enclave SGX_ERROR_ENCLAVE_CRASHED bubbles out of the ECALL
host CONSENSUS FAILURE!!! err="failed to apply block;
error SGX_ERROR_ENCLAVE_CRASHED"
H+1 no honest validator can produce it; chain does not advanceThe malicious diff is a byte choice, not an implementation trick
The PoC modifies one branch of the proposer path in the consensus fork and nothing else. It prepends []byte{0xff} to rpp.Txs when the proposer is producing the target height and logs a marker line for evidence:
// injected by the PoC build in the proposer path only (scrtlabs/tendermint
// state/execution.go), before types.ToTxs(rpp.Txs). this is the byte-choice
// primitive a Byzantine proposer has for free: they choose what goes in
// their own proposal. no software patch is required to exercise it in a
// real attack; the patch here only pins the byte for deterministic
// reproduction.
if os.Getenv("POC_MALFORMED_RAW_TX_HEIGHT") == fmt.Sprint(height) {
rpp.Txs = append([][]byte{{0xff}}, rpp.Txs...)
blockExec.logger.Error("POC: injecting malformed raw tx into proposed block",
"height", height, "tx_hex", "ff")
}
txl := types.ToTxs(rpp.Txs)The point of the patch is determinism for the harness, not capability for the attacker. A Byzantine proposer already has the primitive by definition: they pick the bytes of the transactions in the proposal they gossip. The honest validators in the run below use the unmodified image.
The Azure HW witness
On Standard_DC8s_v3 in eastus (Intel Xeon Platinum 8370C-class, /dev/sgx_enclave and /dev/sgx_provision, SGX_MODE=HW, Azure DCAP az-dcap-client 1.13.1 against https://global.acccache.azure.net/sgx/certification/v3/, secretd check-enclave reporting SGX enclave health status: Success before the run), the malicious val0 produced height 10 with proposer address 4114F98023D24486B3581D763E69E8541D431663. All four RPC endpoints returned matching height-10 blocks:
height = 10
encrypted_random = non-null # normal encrypted-random path
txs = ["/w=="] # base64 for 0xff
block_id.hash = D275E21A6EE100632E03424914F69086E40E8F91A56CEEB24A0D222FD235DE32
parts.hash = EEB4BDD79FA6F55E5EA91D600DC211A80CFC71EE4079EC3075DDD623065C8F5C
data hash = E3F3EDBDC95F66B3CECFAC7F365650BDC9B243ECF2E87C40D36F553D9C952760All four validator logs, including malicious val0, carried the same panic at the same source location:
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: WireError(UnexpectedEof)',
shared/block-verifier/src/txs.rs:23:44
Failed to submit block signatures
error in proxyAppConn.FinalizeBlock err=SGX_ERROR_ENCLAVE_CRASHED
CONSENSUS FAILURE!!! err="failed to apply block; error SGX_ERROR_ENCLAVE_CRASHED"Post-attack /status from all four endpoints reported:
latest_block_height = "10"
latest_block_hash = "D275E21A6EE100632E03424914F69086E40E8F91A56CEEB24A0D222FD235DE32"for the full 300-second capture window. The chain committed height 10 at the consensus layer and then could not apply it. That is the shape of this bug: a committed-but-un-finalizable state that recovery tooling has no obvious hook for.
A four-validator Docker localnet SW witness (one malicious, three honest, 240-second capture) reproduced the same root-cause panic; the process-level post-crash shape differs because whether the container outlives the enclave depends on the runtime harness, but the enclave panic and the halt are identical.
The enclave was not tampered with
Between honest and malicious runtime images, only /usr/bin/secretd differs. The three artifacts that determine enclave and SGX-bridge behavior match byte-for-byte:
c033f743bc5a254abede89cfe749487285d0eb7e7771abefd87766f5940ae266 /usr/lib/librandom_api.so
b7727ec129774a3ce1a97b5b143537e753090b4286ae9c79d1a7224b62f2c1ab /usr/lib/librust_cosmwasm_enclave.signed.so
ee56c94bc283ec6dd8813568f2d11d566c87bcc53f8b761b3fcf24426394c678 /usr/lib/libgo_cosmwasm.soThe panic is inside the genuine Secret-Labs-signed enclave, driven by attacker-controlled input. Nothing about the attack requires modifying the enclave, forging attestation, or breaking the TEE. The malicious host binary changes only what bytes the malicious node proposes; every honest node runs the unmodified enclave and dies inside it. val0 poisoned the well and drank from it.
Why This Matters
Secret Network is a semi-permissioned SGX-attested validator set. Admission is gated; proposer selection is not. Once inside the active set, CometBFT's stake-weighted proposer-priority scheduler selects every validator as proposer with frequency proportional to voting power. A validator with 1% voting power is scheduled as proposer approximately once per 100 heights under that deterministic selection. On a live active-set chain with block cadence measured in single-digit seconds, that translates to multiple proposer slots per hour for any active participant. There is no minimum-stake threshold below which the schedule skips a validator, and there is no >1/3 requirement for this attack. One proposer slot suffices.
The economics favor the attacker. No safety violation means no on-chain slashing rule fires. No double-sign evidence, no light-client evidence, no application-layer misbehavior report is produced. The honest supermajority signs the malformed block with legitimate keys against a proposal that legitimately passes their shipped validation logic. The only stake-side exposure to the attacker is jail-for-downtime if their validator later stops signing. The defender pays for it with a network-wide halt affecting every dApp, every user, every IBC channel, and every governance action until operators coordinate a patched binary.
Secret Labs' own operational posture reinforces the framing. The v1.18 upgrade proposal treats enclave-bricking as a network-wide production risk severe enough to warrant a documented emergency-upgrade procedure. That procedure exists because "enclave crash during finalization" is a class the chain must survive. This finding is one instance of that class, produced by one byte. The x/emergencybutton circuit breaker cannot help, for the same reason it cannot help any consensus-layer halt: it operates above consensus, and blocking consensus blocks the block production that would fire the breaker.
The composition trap also generalizes. Any Cosmos SDK chain that (a) leaves ProcessProposal at the NoOp default, and (b) touches raw proposal tx bytes inside a module BeginBlock before the SDK's tx decode loop runs, inherits the same class of exposure. Secret's SGX enclave is the dramatic example because the crash surfaces as SGX_ERROR_ENCLAVE_CRASHED rather than as a recoverable ErrTxDecode. The same shape appears wherever any pre-decode consumer of raw tx bytes has less tolerant error handling than the SDK's post-decode path.
What This Did Not Allow
- No safety violation. Honest validators do not double-sign, do not fork, do not commit conflicting blocks. Every honest validator that precommits the malformed height precommits the same block hash. The failure is at finalization, downstream of the precommit.
- No forged signatures. Honest validators sign the block with legitimate keys against a proposal that legitimately passes Tendermint validation,
ProcessProposal, and consensus voting rules. The bug is that those rules do not enforce Cosmos-tx decodability. - No enclave compromise. The three enclave and SGX-bridge artifacts are byte-identical between honest and malicious runtime images. No enclave code is modified, no attestation is forged, no sealed data is accessed. Remote attestation reported healthy end-to-end.
- No exfiltration of enclave secrets, seeds, or sealed data. The enclave aborts on the parser panic before it does any work with the malformed tx.
- No permissionless validator entry. The attacker must operate or compromise an existing active-set validator. Secret's admission remains semi-permissioned.
- No permanent chain destruction. Coordinated recovery via a patched binary rollout remains possible. The window between exploitation and recovery is the window in which the network is down.
- No exploit reachable via public RPC. CheckTx and mempool admission rules can reject malformed transactions before they reach the mempool. The relevant adversary is a Byzantine proposer, which bypasses those rules by construction, not an external RPC caller.
Severity Classification
Bluethroat classification: Critical (chain halt). Severity refers to the pre-patch state at the time of report; the shipped v1.25.0 release closes the trigger.
Immunefi's blockchain/DLT severity classification system v2.3 places outcomes that prevent a network from confirming new transactions, and total network shutdown, in the Critical tier. The demonstrated effect satisfies that bar: after the malformed height commits at the consensus layer, no honest validator can apply the block, and therefore no validator can produce the next height. The trigger is a single Byzantine proposer at one slot, the textbook BFT-tolerated fault. Consensus liveness in a fork of CometBFT is expected to continue with less than one-third Byzantine voting power. It does not, here, with one-quarter.
The severity rests on a mechanically forced construction. x/compute.BeginBlock is the sole caller of api.SubmitBlockSignatures; that call is executed by BaseApp before the SDK tx decode loop; the enclave-side parser unwraps. There is no second gate between the malformed byte and the panic on the shipped code path. Secret Labs shipped a fix at Critical-appropriate speed (eight calendar days from report to release).
Fix
Shipped in SecretNetwork commit 7eec819ef45fb4d08b0604233ca32bb8fa6685e0, subject "skipping malformed txs in submit_block_signatures", packaged in a SecretNetwork release on 2026-06-10. The patch removes the internal unwrap() in tx_from_bytes so that the parser error propagates to the caller, and the companion change in submit_block_signatures.rs handles that error rather than crashing:
// AFTER, cosmwasm/enclaves/shared/block-verifier/src/txs.rs
// (see linked fix commit for the exact post-fix signature)
// the .unwrap() is gone. tx_from_bytes now returns the parser error
// to its caller instead of panicking; submit_block_signatures.rs
// consumes that error and skips the malformed tx rather than taking
// the enclave thread down.
pub fn tx_from_bytes(raw_tx: &[u8]) -> /* ProtobufResult<Tx> */ ... {
Tx::parse_from_bytes(raw_tx)
}The fix answers the compositional question by declaring, in the enclave code, that malformed raw txs are a normal condition to be skipped rather than an unreachable state. That is a legitimate design choice given the constraint that the enclave sees the txs first. It does not add a ProcessProposal handler, it does not move the SGX call to after the SDK tx decode loop, and it does not change Tendermint's opaque-bytes contract.
Defense-in-depth items not covered by the shipped patch, offered as follow-up:
- Install a real
ProcessProposalhandler inNewSecretNetworkAppthat at minimum attempts a fullcosmos.tx.v1beta1.Txdecode for every proposal tx and rejects the proposal on failure. This restores the consensus-time gate that the SDK'sNoOpProcessProposalfallback removed. - Audit every module
BeginBlockandEndBlockfor any other consumer of rawctx.TxBytes()that runs before the SDK tx decode loop.tx_from_byteswas one line; other pre-decode consumers of raw tx bytes may have the same shape. - Audit remaining
unwrap()and.expect()call sites inside the enclave that operate on attacker-influenceable inputs. - Add regression tests: a proposal whose tx list contains
0xff; a proposal containing a truncated valid Cosmos tx; a proposal containing a wire-type-mismatched protobuf. A 4-validator integration test where one proposer includes such a proposal and honest validators must reject it, not precommit and crash.
Timeline
- 2026-06-02 -- Finding reported to Secret Labs under coordinated disclosure with the full bundle (report, SW witness, Azure real-SGX HW witness, PoC patches, provenance hashes).
- 2026-06-09 -- Fix commit
7eec819ef45fb4d08b0604233ca32bb8fa6685e0authored by Secret Labs. - 2026-06-10 -- Fix released in a SecretNetwork release on the v1.25 line.
- 2026-07-28 -- Public writeup published following the coordinated-disclosure window.
Key Takeaway
Composition is an attack surface. When three layers each hand a responsibility to somebody else, one of them is wrong, and the answer is architectural, not local. Every optional check across a trust boundary needs a named owner, and "the layer below will catch it" is not an owner.
If you carry one rule out of this to other systems: when a module reads attacker-controlled inputs before the generic decoder does, that module inherits the decoder's failure obligations too. SDK's ErrTxDecode is genuine, and it works, for chains that do not shove raw bytes into an unrecoverable layer inside BeginBlock. unwrap() on attacker-controlled input is a bug in ordinary Rust; inside an SGX enclave sitting downstream of an unauthenticated consensus-time gate, it is an availability primitive.
References
scrtlabs/SecretNetworkrelease v1.24.1 -- mainnet release line affected by this finding.scrtlabs/SecretNetworkrelease v1.25.0 -- release anchor for the patched build.scrtlabs/SecretNetworkfix commit7eec819ef45fb4d08b0604233ca32bb8fa6685e0-- "skipping malformed txs in submit_block_signatures". Post-fixtx_from_bytessignature and caller diff live here; treat the commit as authoritative for the exact return type.- Consensus fork:
scrtlabs/tendermint@v0.38.23-secret.1--types/tx.goopaque-bytes contract. - Cosmos SDK fork:
scrtlabs/cosmos-sdk@v0.50.14-secret.12--NoOpProcessProposaldefault andFinalizeBlockordering (baseapp/abci.go,baseapp/abci_utils.go). - Secret Labs, How Secret Network Uses SGX -- SGX and consensus coupling in production.
- Secret Labs, Secret Network v1.18 Upgrade Proposal -- enclave-bricking treated as network-wide production risk.
- Immunefi Vulnerability Severity Classification System v2.3 -- severity-tier reference.
- KuCoin, coverage of Secret Network's proposed migration to Arbitrum, 2026-07-07 -- ecosystem context.
Bluethroat Labs is a security research firm focused on smart contracts, distributed consensus, and TEE-integrated systems. This is finding 2 of 4 disclosed to Secret Labs in this engagement; companion writeups will follow as vendor timelines allow.
