The Secret Network Research Path
The Parser Behind the Governance Gate
How Stargate, raw witnesses, and an unchecked slice halt validators
By Rahul Saxena (@saxenism)
Severity: Critical (a deterministic four-validator halt and restart-persistent crash were demonstrated; mainnet reachability requires an active-set Byzantine proposer and assumes no production-only policy outside the audited source blocks the contract route).
Affected component: the then-current scrtlabs/SecretNetwork repository at
commit 266cd4fd96e816656f520ba85d309cc5b1c78ef9, release tag v1.24.1,
now publicly maintained as Secret3dev/SecretNetwork; x/compute Stargate
dispatch, SGX block-message verification, and the machine-whitelist parser.
Full revision table in the
technical appendix.
Fix: The Secret3dev community's v1.27.2 rejects privileged compute
messages from contract Stargate, re-checks machine-whitelist governance in the
message server, bounds-checks the SGX parser, and propagates enclave approval
failures. Released and activated on secret-4 on 2026-09-23.
Disclosure: first submitted to Secret Labs under coordinated disclosure on
[DATE TO CONFIRM]; later fixed by community maintainers in the Secret3dev
organization; fix released and activated on mainnet 2026-09-23.
Secret Network copied every top-level transaction message's raw Any.Value into
an SGX cache before normal SDK execution. A Byzantine proposer could combine
those bytes with a contract-emitted MsgUpdateMachineWhitelist, bypassing the
top-level governance check and making SGX parse a malformed witness whose
declared machine_id length exceeded the remaining input.
The parser sliced to the attacker-declared length without checking the buffer. Honest validators then panicked while finalizing the same block. In software the four validator processes exited and a restarted validator crashed again while replaying the poisoned block. A separate four-validator Azure run reproduced the SGX panic on real hardware and stopped at the next height.
This writeup describes a vulnerability first disclosed by Bluethroat Labs to Secret Labs under coordinated disclosure and later remediated by the community maintaining Secret Network in the
Secret3devorganization. All observations reflect the audited revisions above. No transaction was broadcast tosecret-4; all positive runs used isolated local networks and researcher-controlled SGX hardware.
The chain, in five steps
- Construct. An active-set Byzantine proposer places a valid contract
execution first and an outer-protobuf-valid witness transaction second.
The witness contains a malformed
MsgUpdateMachineWhitelistvalue. - Capture.
SubmitBlockSignaturesverifies the block data hash and copies both transactions' raw message values intoVERIFIED_BLOCK_MESSAGESbefore normal SDK execution. - Bypass. The contract emits
MsgUpdateMachineWhitelistthrough Stargate. The dispatcher checks basic validity and contract signer equality, then routes directly to the message server without the top-level governance ante check. - Crash.
OnApproveMachineIDasks SGX to find the machine ID in the raw witness cache.is_msg_machine_idreads length 127 with one byte remaining and performs an out-of-bounds slice. - Persist. Validators panic while applying the block. A normal restart replays the same block and reaches the same parser panic.
The software witness crashed all four validators at the height-82 finalization boundary. The hardware witness committed height 22 with all four signatures, then remained at height 22 while consensus repeatedly attempted height 23. Receipts and the distinction between those two measurements are in the technical appendix.
Why this is a consensus problem
The trigger is not an ordinary mempool transaction. The malformed witness is expected to fail normal ante or SDK handling. A proposer, however, chooses the transaction bytes and order in its own proposal. Secret's SGX verifier treated those data-hash-consistent bytes as authorization evidence before the application decided whether their messages were valid.
BFT consensus is expected to retain liveness with less than one-third Byzantine voting power. This path required one active-set validator and one proposer slot, not one-third of voting power. Every honest validator processing the proposal entered the same deterministic parser path.
Security property violated. Proposer-selected raw block bytes must not become trusted SGX authorization evidence unless they have passed the same semantic checks as the operation they authorize.
Root cause
1. Contracts could route a governance-sensitive SDK message
The Stargate encoder unpacked a contract-provided Any, and the SDK message
handler checked ValidateBasic and signer equality before calling the normal
router. It did not execute the top-level ante decorator.
Vulnerable-source excerpt
(handler_plugin.go:580-603);
... marks routing code omitted for brevity:
if m, ok := msg.(sdk.HasValidateBasic); ok {
if err := m.ValidateBasic(); err != nil {
return nil, errorsmod.Wrapf(err, "failed basic validation for message type %T", msg)
}
}
signers, _, err := h.cdc.GetMsgV1Signers(msg)
// ...
for _, acct := range signers {
if !contractAddr.Equals(sdk.AccAddress(acct)) {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission")
}
}The governance-to-message comparison existed only in the top-level ante path.
The UpdateMachineWhitelist message server parsed the IDs and called
api.OnApproveMachineID without repeating that check.
2. SGX cached raw message values before SDK acceptance
After validating the Tendermint data hash, the enclave parsed each outer
transaction and appended every message's Any.Value.
Verbatim vulnerable source
(wasm_messages.rs:38-42):
pub fn append_msg_from_tx(&mut self, mut tx: Tx) {
for msg in tx.take_body().messages {
self.messages.push_back(msg.value);
}
}The hardware run made the mismatch observable: the contract transaction
succeeded, the top-level witness later returned tx parse error, and SGX had
already consumed that witness's raw inner value.
3. The witness parser trusted its length field
The parser read the protobuf length and sliced without comparing it with the remaining cursor.
Verbatim vulnerable source
(offchain.rs:784-790):
if let Some(x) = r.read_uint() {
r.cursor = &r.cursor[0..x];
} else {
trace!("wrong sub6");
return false;
};The witness encoded x = 127 with one byte left. Rust panicked with range end index 127 out of range for slice of length 1.
Reproduction
| Layer | Establishes | Does not establish |
|---|---|---|
| Go route preflight | Contract Stargate reaches UpdateMachineWhitelist; the equivalent top-level message fails without matching governance | SGX execution or a consensus halt |
| Four-validator software localnet | Ordered attack pair, all four exits at the height-82 finalization boundary, and restart replay into the same panic | A clean height-82 RPC commit record or real SGX hardware |
| Four-validator Azure SGX localnet | Real-SGX parser panic; common height-22 block and commit; four signatures; no progress beyond height 22 | A positive run with the complete production,verify-validator-whitelist feature set |
| Mainnet-release artifact run | v1.24.1 release-path provenance and byte-identical enclave/bridge artifacts between honest and proposer images | Exploitation using those exact precompiled images |
| Public source review | light-client-validation includes the vulnerable scanner in the mainnet build path | An unknown policy outside the audited checkout |
No single run executes the entire sequence from the exact mainnet release image through a production
secret-4block. The evidence is compositional. Source review supplies the feature-gate and dispatch links between the measured layers.
The malformed input is an outer-protobuf-valid transaction whose first
Any.Value resembles MsgUpdateMachineWhitelist but declares a 127-byte
machine-ID field with one byte available. The production trigger requires no
failure injected into honest validators; the proposer image deterministically
inserts the two transaction bytes to model Byzantine proposal construction.
Identities, hashes, commands, and full receipts are in the technical appendix.
Halt reachability
The demonstrated result is a deterministic localnet halt. In software, every validator exited while applying height 82; one restarted validator replayed height 82 and exited again in about four seconds. On Azure SGX, all four RPC endpoints agreed on the two-transaction height-22 block and its four-signature commit. Post-trigger monitoring retained height 22 across 1,120 status and 1,120 consensus-state samples while the network attempted height 23.
Production reachability requires an active-set Byzantine validator to obtain a
proposer slot and a contract able to emit the Stargate message. The public
v1.24.1 source enabled Stargate, registered the sensitive SDK message, and
included the scanner under mainnet's light-client-validation feature. The
positive hardware run did not use the full mainnet feature set, and Bluethroat
could not observe whether an external deployment policy restricted contract
upload, code IDs, or message type URLs.
What this did not allow
- No ordinary remote-user trigger was demonstrated. The malformed witness was proposer-inserted block data and would likely fail normal mempool handling.
- No safety split was observed. The measured effect was loss of liveness, not conflicting finalized histories.
- No live-network transaction was sent. Every positive run used an isolated four-validator network.
- No complete end-to-end production run was performed. The Reproduction table states where composition stops.
Severity
Bluethroat Labs classification: Critical, with a Byzantine-proposer reachability qualifier.
The software and real-SGX witnesses demonstrate that one proposer can select a
block which deterministically panics honest validator SGX execution and prevents
normal restart recovery. The remaining uncertainty is production reachability:
the hardware witness used light-client-validation,random, while the
mainnet-feature connection is source-derived and any deployment policy outside
the audited checkout was unobservable. Critical is the strongest classification
supported if active-set Byzantine proposers are in scope; otherwise the
precondition materially lowers practical exposure.
Fix
The Secret3dev community's public v1.27.2 tag closes the demonstrated chain at
multiple boundaries. Contract Stargate rejects both privileged message type URLs. The
UpdateMachineWhitelist message server repeats the governance check, enclave
approval errors now fail the message, and the parser checks the remaining
length before slicing.
Verbatim patched source
(offchain.rs:797-806):
if let Some(x) = r.read_uint() {
if x > r.cursor.len() {
trace!("malformed field");
return false;
}
r.cursor = &r.cursor[0..x];
} else {
trace!("wrong sub6");
return false;
};The tag includes a malformed-length regression test and a privileged-message
denylist covering MsgUpdateMachineWhitelist. Bluethroat verified these changes
against the public source tag; it did not rebuild or test the signed enclave
running on secret-4.
Timeline
- 2026-06-02 — Software and Azure SGX evidence captured.
- [DATE TO CONFIRM] — Finding first submitted to Secret Labs under coordinated disclosure with software, hardware, proof-of-concept, and integrity evidence.
- 2026-09-23 — The Secret3dev community released
v1.27.2; it was activated onsecret-4at height 27,286,266. - 2026-11-10 — Bluethroat verified the parser, privileged-message, message
server, and error-propagation fixes in the public
v1.27.2source tag. - [DATE TO CONFIRM] — Secret Labs maintainers identify this finding as eligible for publication, subject to draft review.
Key takeaway
Cryptographic inclusion proves that bytes were proposed; it does not prove that the application accepted their meaning. An enclave cache of raw block messages cannot safely stand in for governance or successful SDK execution.
For TEE-integrated chains, the audit question is whether privileged enclave state consumes proposer-controlled bytes before the application has applied the authorization rules for those bytes. Here, that ordering turned a rejected witness into a validator-wide parser crash.
References
- Secret Network
v1.24.1source tag - Contract Stargate dispatch
- Raw message capture
- Vulnerable machine-ID parser
- Secret Network
v1.27.2release v1.27.2privileged message definitionsv1.27.2Stargate denylistv1.27.2centralized governance check
Technical Appendix: Secret Network Machine-Whitelist Parser Halt
Supporting material for the main writeup. This appendix records the revisions, source bridge, artifact identities, measured outputs, controls, and limitations behind the article.
1. Pinned revisions
| Component | Revision |
|---|---|
| Software PoC checkout | edcbccbbbe65cae1bd93d4d9089ff5d98228f704 |
| Hardware PoC and confirmed affected release | scrtlabs/SecretNetwork v1.24.1, commit 266cd4fd96e816656f520ba85d309cc5b1c78ef9 |
| Community remediation source | Secret3dev/SecretNetwork v1.27.2, commit 72c548f39602e87924f69289715ae8d7c8cfa7b4 |
| Software enclave features | debug-print,random,light-client-validation |
| Hardware positive-run features | random,light-client-validation |
Public mainnet workflow features at v1.24.1 | verify-validator-whitelist,light-client-validation,random,production |
The finding was first submitted to Secret Labs. Its eventual remediation was
shipped by community maintainers in the Secret3dev organization.
The public v1.24.1 tag contains the same unchecked slice at tag-local
cosmwasm/enclaves/execute/src/registration/offchain.rs:785-786. Its release
workflow selects hardware SGX and includes light-client-validation.
The public v1.27.2 tag was inspected on 2026-11-10. The relevant fixes are:
handler_plugin.go:463-483: contract Stargate rejects privileged type URLs;privileged.go:292-319: machine-whitelist governance validation is shared;msg_server.go:347-391: the message server checks governance and propagates enclave failure;offchain.rs:797-806: malformed lengths returnfalsebefore slicing;offchain.rs:831-837: a malformed-length regression test.
2. Release-path provenance
The software localnet used separate honest and proposer images. The proposer image differed in the host daemon and contained the deterministic insertion marker. The enclave and bridge artifacts matched:
librandom_api.so
c033f743bc5a254abede89cfe749487285d0eb7e7771abefd87766f5940ae266
librust_cosmwasm_enclave.signed.so
b7727ec129774a3ce1a97b5b143537e753090b4286ae9c79d1a7224b62f2c1ab
libgo_cosmwasm.so
ee56c94bc283ec6dd8813568f2d11d566c87bcc53f8b761b3fcf24426394c678The host daemons differed as expected:
honest d8ff10460467bc09cfc0c7e5b64c8f1c0048cb77a7bd30fc7071c5f1c21a1653
proposer 94d119f5840e6d0b7223038cfb6a0bfe349bea7599c281db32b4242cb94ade97A separate Azure run built through the v1.24.1 mainnet-release Docker path and
fetched the /v124/ precompiled enclave and bridge blobs. It established
release-path provenance but did not progress far enough to serve as the positive
exploit witness. The source-built hardware run is the positive SGX receipt.
3. Evidence layers
Layer 1 — contract-route preflight
The Go preflight contained two controls:
TestStargateReachesUpdateMachineWhitelistWithContractSignerreached the message server when the contract address was the SDK message signer.TestTopLevelUpdateMachineWhitelistRequiresGovAnterejected the equivalent top-level path without a matching passed proposal.
This isolates the dispatch difference. It does not execute SGX.
Layer 2 — four-validator software localnet
Configuration:
validator count 4
voting power equal
proposer image val0 only
honest images val1, val2, val3
SGX_MODE SW
attack threshold H80
actual insertion H82
consensus timeout 700msThe proposer inserted two transactions:
- a valid signed contract execution whose response emitted
/secret.compute.v1beta1.MsgUpdateMachineWhitelist; - an outer-protobuf-valid top-level transaction whose first
Any.Valuedeclared a 127-bytemachine_idfield with one byte remaining.
All validator excerpts reached the height-82 consensus/finalization boundary, dispatched the Stargate message, and logged:
thread '<unnamed>' panicked at
'range end index 127 out of range for slice of length 1',
execute/src/registration/offchain.rs:795:21Container state:
val0 Exited (2)
val1 Exited (2)
val2 Exited (2)
val3 Exited (2)The final successful RPC status sample remained at height 81 on all validators. The height-82 block and commit RPC files are empty because RPC disappeared while the block was being applied. This layer therefore establishes a common consensus/finalization boundary and the four crashes, not a clean RPC proof that height 82 committed.
Layer 3 — restart replay
The val1 restart receipt recorded:
ABCI Replay Blocks appHeight=81 stateHeight=81 storeHeight=82
Replay last block using real app
[DispatchSubmessages] height=82 ... type=stargate
thread '<unnamed>' panicked at
'range end index 127 out of range for slice of length 1',
execute/src/registration/offchain.rs:795:21
github.com/cometbft/cometbft/consensus.(*Handshaker).replayBlock(...)The process exited with code 2 before restart and code 2 again about four seconds after restart. Normal process restart did not remove the poisoned block from replay.
Layer 4 — four-validator Azure SGX localnet
Environment:
Azure SKU Standard_DC8s_v3
region eastus
SGX mode HW
enclave health Success
target commit 266cd4fd96e816656f520ba85d309cc5b1c78ef9
features random,light-client-validation
malicious proposer val0
honest validators val1, val2, val3
actual insertion H22All four RPC endpoints returned the same attack block:
height 22
encrypted_random non-null
transaction count 2
block hash 2B9289884A8E98A58EB27B588FE7914B393E16AF19B996F64E723293E8983E9D
parts hash 91C0BFE517D52BD059A5687E5EEC2BA86243C340798FAB72B47A477B8BE3B1CAAll four commit captures returned height 22, the same block hash, and four signatures. All four block-results captures returned:
tx0 code=0
tx1 code=2 log=tx parse errorThis split is expected. SGX copied the raw witness value while processing block signatures; normal SDK execution rejected the witness later.
Honest validator logs contained:
thread '<unnamed>' panicked at
'range end index 127 out of range for slice of length 1',
execute/src/registration/offchain.rs:786:21
Failed to add machine_id: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaThe next-height path reported:
CONSENSUS FAILURE!!! err="Failed to submit validator set to enclave"Post-trigger monitoring recorded:
status samples 1120
consensus-state samples 1120
latest committed height 22
consensus state attempting H23 with nil/failure statesThe dedicated cloud resources were deleted after capture. The teardown sweep reported no remaining VMs, disks, public IPs, NICs, snapshots, or tagged resources.
4. Prepared-input identities
The software receipt recorded these SHA-256 values:
tx0-exec.bin eadb0615449c904be81e9ac70bd7f6aa8d2c3bde04129ec25dd03bc17a62aca9
tx1-witness.bin e06a348b28fbb946db1630ec4bb0db35ab6d19b5c0611edea085eee98c9d29f4
msg-valid.bin 2511e95ff44ad07ad3ec23f3047bdbed9bde4c3f19aff6ed890018bb8facba1a
msg-malformed.bin 9b4b9c675620c50f6d1d7243733b21d7f00cd9949a53cf6ee3646445aa251eecThe malformed value preserves a parseable outer Tx. Its invalidity is inside
the first message's Any.Value, after the sender and proposal fields:
field tag 0x1a
declared length 0x7f
remaining field bytes 1That distinction matters because an earlier remediation skipped transactions whose outer protobuf parse failed. It did not make this inner parser safe.
5. Feature-gate bridge
The positive hardware witness enabled light-client-validation,random. The
public mainnet workflow also enabled production and
verify-validator-whitelist.
The source bridge is:
light-client-validationenablescheck_machine_id_in_block.ecall_onchain_approve_machine_idcalls that scanner before approval.productionandverify-validator-whitelistdo not replaceis_msg_machine_idor add a check around its length-derived slice.MsgUpdateMachineWhitelistreachesapi.OnApproveMachineIDin the affected message server.- The compute keeper advertises Stargate support and registers the message service.
This establishes code inclusion. It does not substitute for a positive full-mainnet-feature reproduction.
6. Evidence locations in the disclosure bundle
Primary software run:
evidence/sw-4val-poc/
machine-whitelist-sgx-parser-crash-sw-emitter-mvp-20260602T095521Z/Restart receipt:
evidence/sw-4val-poc/
machine-whitelist-sgx-parser-crash-sw-emitter-mvp-20260602T095521Z/
evidence/restart-replay-val1-20260602T100352Z/Primary hardware run:
evidence/azure-sgx-hw-poc/
secret-sgx-hw-machine-whitelist-positive-20260602T115118Z/
remote/success/
machine-whitelist-sgx-parser-crash-hw-positive-v124-20260602T175439Z/Mainnet-release artifact run:
evidence/mainnet-release-artifact-provenance/
secret-sgx-hw-mainnet-artifact-20260602T101830Z/These paths are provenance for a disclosure-bundle reviewer. They are not public download links.
7. Limitations
- The trigger requires proposer control; no ordinary mempool path was demonstrated.
- The software run lost RPC while height 82 was being applied. The hardware run supplies the clean block-and-commit receipt.
- The hardware positive run was source-built with
random,light-client-validation, not the complete mainnet feature set. - The exact precompiled mainnet-release images did not progress far enough to become a positive exploit witness.
- A production-only contract upload, code-ID, or type-URL policy outside the audited source could affect reachability.
- The work demonstrates liveness loss, not a safety violation.
8. Safety
No transaction was broadcast to secret-4. All blocks, contracts, machine IDs,
and keys were researcher-controlled fixtures on isolated networks. Cloud
resources were deleted after capture. No production hardware identifier,
credential, private key, PPID-bearing quote, or unredacted cloud response is
included here.

