Footer
General•November 09, 2026

The Secret Network Research Path

The Unchecked Seed Export

By Rahul Saxena (@saxenism)

Host-Directed Genesis Seed Export from Secret Network's Production SGX Enclave

By Rahul Saxena (@saxenism)

Severity: High — requires a legitimately seeded node

Affected component: Secret3dev/SecretNetwork at commit 2b8e18788e70d79245343ed53bc2cc42a345d2d4 (v1.26.0-community-continuance), including the production execute enclave librust_cosmwasm_enclave.signed.so, host library libgo_cosmwasm.so, and the public ecall_get_genesis_seed path.

Fix: v1.27.2, released 2026-09-23 and activated on secret-4 at height 27,286,266 (2026-09-23 12:29:45 UTC) under governance proposal 377; v1.27.3 later retained the enclave. Secret states that the shipped v1.27 line refuses a genesis-seed recipient that is not attested; the public v1.27.2 source tag shows the export entrypoint refusing every request.

The production Secret Network enclave exposed a public ecall_get_genesis_seed(pk) entrypoint. A node host supplied 32 bytes of its choosing. The enclave checked only that the input was 32 bytes long, selected KEY_MANAGER.consensus_seed.arr[0], and encrypted that seed to the supplied X25519 public key.

Because the host chose the recipient keypair, it held the matching private key. The seed-0 exchange public key was also available through ecall_get_network_pubkey(0). Those values let the host complete the Diffie-Hellman exchange, decrypt the 48-byte response outside SGX, and recover seed 0.

This was a local call against the operator's own provisioned node. It required no transaction, governance action, forged attestation, SGX side channel, modified enclave, or vulnerable processor.

Attack path

  1. Operate a legitimately seeded node. The production enclave already contains the network's consensus-seed vector in its sealed key manager. Obtaining admission and provisioning is a prerequisite, not part of this finding.
  2. Choose a recipient key. The host generates an X25519 keypair (sk, pk).
  3. Call the shipped ABI. The host invokes get_encrypted_genesis_seed(pk) through libgo_cosmwasm.so.
  4. Receive seed 0 wrapped to the host key. The enclave derives the seed-0 exchange key, computes X25519 against pk, and returns AES-SIV(seed_0) with pk as associated data.
  5. Recover seed 0 on the host. Using sk and the public seed-0 exchange key, the host derives the same shared secret and decrypts the response.

The recovered value is seed 0, not the latest rotated seed. Its demonstrated confidentiality reach is therefore limited to historical transaction inputs encrypted to the seed-0 network I/O key and any old-format state that remains decryptable through the seed-0 state-IKM fallback.

Root cause

The public entrypoint checked length, not recipient authorization

The public EDL entrypoint accepted raw recipient bytes without an identity or authorization object.

Verbatim upstream source (cosmwasm/enclaves/execute/Enclave.edl:94-98, Secret3dev/SecretNetwork@2b8e1878):

public sgx_status_t ecall_get_genesis_seed(
    [in, count=pk_len] const uint8_t* pk,
    uintptr_t pk_len,
    [out, count=48] uint8_t* seed
);

The enclave handler required only a 32-byte input before selecting seed 0 and passing it to the seed-exchange primitive.

Upstream excerpt (cosmwasm/enclaves/execute/src/registration/offchain.rs:484-501, Secret3dev/SecretNetwork@2b8e1878). Code lines are verbatim; // ... marks omitted upstream lines (a warn! log call and a trace! call):

if pk_slice.len() != PUBLIC_KEY_SIZE {
    // ...
    return Err(sgx_status_t::SGX_ERROR_UNEXPECTED);
}

let mut target_public_key: [u8; 32] = [0u8; 32];
target_public_key.copy_from_slice(pk_slice);
// ...

let seeds = KEY_MANAGER.get_consensus_seed().unwrap();
let res: Vec<u8> = encrypt_seed(&target_public_key, &seeds.arr[0], true)
    .map_err(|_| sgx_status_t::SGX_ERROR_UNEXPECTED)?;

No attestation result, registration record, verified block, or caller identity was consulted on this path.

The exchange primitive encrypted seed 0 to the supplied key

With its is_legacy argument set, as the export handler set it, encrypt_seed derived the seed-0 exchange keypair, performed Diffie-Hellman against the caller-supplied public key, and used the result to encrypt the selected seed.

Upstream excerpt (cosmwasm/enclaves/execute/src/registration/seed_exchange.rs:9-50, Secret3dev/SecretNetwork@2b8e1878). Code lines are verbatim; // ... marks omitted upstream lines (a trace! call and the match arms that check result length):

pub fn encrypt_seed(
    new_node_pk: &[u8; PUBLIC_KEY_SIZE],
    seed_to_share: &Seed,
    is_legacy: bool,
) -> SgxResult<Vec<u8>> {
    let kp0: enclave_crypto::KeyPair;

    let base_seed = if is_legacy {
        let seed0 = KEY_MANAGER.get_consensus_seed().unwrap().arr[0];
        kp0 = Keychain::generate_consensus_seed_exchange_keypair(&seed0);
        &kp0
    } else {
        KEY_MANAGER.seed_exchange_key().unwrap()
    };

    let shared_enc_key = base_seed.diffie_hellman(new_node_pk);

    let authenticated_data: Vec<&[u8]> = vec![new_node_pk];

    // encrypt the seed using the symmetric key derived in the previous stage
    // genesis seed is passed in registration

    // ...
    let res = match AESKey::new_from_slice(&shared_enc_key)
        .encrypt_siv(seed_to_share.as_slice(), Some(&authenticated_data))
    {
        // ...
    };

Binding the ciphertext to new_node_pk as associated data protected integrity, but did not establish that the recipient was authorized.

The authenticated transfer controls were on a different path

The on-chain sharing path in ecall_authenticate_new_node required the certificate to appear in a verified block, verified the DCAP quote and MRENCLAVE, and obtained the recipient key from authenticated quote report_data. None of those checks ran in ecall_get_genesis_seed.

The last in-tree caller of the off-chain export, the createOldSecret upgrade helper, was removed on 2025-10-29. The public EDL entrypoint, enclave handler, SGX bridge, C ABI, and Go wrapper remained reachable through the shipped host library.

Reproduction and evidence boundary

The strongest witness ran on an Azure SGX VM using the unmodified signed v1.26 production enclave and shipped libgo_cosmwasm.so. It loaded two distinct synthetic test seeds through the production init_node path, called the public export with a host-chosen key, and received a 48-byte ciphertext. A second process reopened only the sealed store and returned the same roots and ciphertext. An external verifier then recovered the synthetic seed 0 outside SGX and reproduced both public roots emitted by the enclave; wrong-key and wrong-associated-data controls failed as expected.

No real Secret Network seed, sealed mainnet key-manager file, transaction plaintext, or contract state was used. The production-enclave witness proves export and host-side recovery of seed 0 using synthetic test seeds loaded into the unmodified signed production enclave; it does not reproduce node admission or extract a mainnet secret. Separate software witnesses establish that possession of seed 0 is sufficient to decrypt a synthetic seed-0 transaction input and a synthetic old-format state value under the v1.26 derivations and layouts, while current transaction traffic and current-format state encrypted under later seeds remain outside the demonstrated reach. The v1.27 source observations were captured on 2026-09-07, before the stated shipped fix, and are historical snapshots rather than evidence about the final v1.27.2 enclave.

Full artifact hashes, platform details, receipts, negative controls, source anchors, and the historical v1.27 snapshot are preserved in the technical appendix.

Confidentiality impact

Historical transaction inputs

Seed 0 deterministically derives the seed-0 network I/O keypair. Historical transaction inputs encrypted to that public key remain in immutable chain history. The software witness reproduced the v1.26 derivation, message layout, X25519 exchange, nonce KDF, and AES-SIV decryption using synthetic inputs; a wrong seed and wrong nonce were rejected.

Remaining old-format state

The v1.26 code retained fallback decryption of old-format values with consensus_state_ikm.arr[0]. Migration occurred when an old value was read through a write-permitted path, so dormant or otherwise unmigrated old-format values could remain dependent on seed 0. The software witness reproduced that old-format derivation and decryption with synthetic data; a wrong seed, wrong contract key, and modified associated data were rejected.

Remediation

Secret's shipped fix

Secret states that the shipped v1.27 enclave refuses a genesis-seed recipient that is not attested. The fix was packaged in v1.27.2, released on 2026-09-23 and activated on secret-4 at height 27,286,266 under governance proposal 377; v1.27.3 later retained the enclave.

The public v1.27.2 source tag goes further than that description. The ecall_get_genesis_seed entrypoint remains declared, but its handler now zeroes the output buffer and returns an error without touching the seed vector.

Verbatim upstream source (cosmwasm/enclaves/execute/src/registration/offchain.rs:531-538, Secret3dev/SecretNetwork@v1.27.2; the two comment lines are upstream):

// Public ecall must not return genesis consensus seed[0] without
// attestation+export policy. This ABI has no attestation; empty/error, not seeds.
seed.fill(0);
warn!("ecall_get_genesis_seed refused: attestation+export policy required");
sgx_status_t::SGX_ERROR_ECALL_NOT_ALLOWED

This describes the public source tag. Bluethroat did not rebuild or test the signed enclave running on secret-4 and did not receive maintainer regression-test output, so it does not independently verify that the deployed binary matches that source.

Bluethroat's proposed controls

Bluethroat separately tested a minimal control against the vulnerable v1.26 source. It bound the recipient to the enclave's sealed registration public key and rejected a host-chosen key before reading seed 0. That proposal demonstrated one way to close the reported host-chosen-key path; it is not the control Secret says it shipped.

Because the application no longer had an honest caller, Bluethroat's preferred remediation was to remove the EDL entrypoint, enclave handler, SGX bridge, C ABI, and Go wrapper. If genesis-seed transfer must remain, the recipient should instead be bound to a fresh verified attestation under the active enclave policy, with lifecycle or single-use authorization. A check only in the Go wrapper would be insufficient because the host can call the exported C ABI directly.

The proposed patch and its receipt are included in the technical appendix.

Timeline

  • 2026-09-06 — Reproduced on Azure SGX by loading synthetic test seeds into the unmodified signed v1.26 production enclave and invoking the shipped host library.
  • 2026-09-07 — Captured a historical source-level snapshot of the public v1.27 branch at commit 91145f355c45fd2464ec486fc800a986cd3b5e2b; finding reported to the Secret Network maintainers under coordinated disclosure.
  • 2026-09-21 — Governance proposal 377 submitted to upgrade secret-4 to v1.27.2 at height 27,286,266; it passed on 2026-09-22.
  • 2026-09-22 — Maintainers confirmed remediation in the v1.27 line.
  • 2026-09-23 — v1.27.2 released; secret-4 upgraded at height 27,286,266, block time 12:29:45 UTC (v1.27.3 followed later and retained the enclave).
  • 2026-10-15 — Maintainers cleared the finding for publication.

References

SEEDX-001 Technical Appendix

This appendix preserves the provenance, receipts, negative controls, and source anchors for Host-Directed Genesis Seed Export from Secret Network's Production SGX Enclave.

All seeds used in the research were synthetic test fixtures. No Secret Network consensus seed, mainnet sealed key-manager file, production secret, transaction plaintext, or contract state appears here. The strongest witness loaded synthetic test seeds into the unmodified signed production enclave. The ephemeral hardware Machine ID is redacted because it is not needed to reproduce the finding.

1. Pinned revisions

ComponentRevision
NetworkSecret Network mainnet, secret-4
Affected public releasev1.26.0-community-continuance
Application commit2b8e18788e70d79245343ed53bc2cc42a345d2d4
Source repositorygithub.com/Secret3dev/SecretNetwork
Historical v1.27 source snapshot91145f355c45fd2464ec486fc800a986cd3b5e2b, observed 2026-09-07
Fixed enclave tree identified by maintainersgithub.com/Secret3dev/SecretNetwork-v1.27, branch v1.27, HEAD 268c8c74b9a471e3c77d53f67535d35db3946999
Fix releasev1.27.2, released 2026-09-23
Production MRENCLAVE2b9a69d612bbaa4d038cf2ae8c72245dabacd0650c327d2e25dffdd979dfa80d
Enclave MRSIGNER845cf34814f45595c720f81f74794d785931484f4405d6e5036ef88713ff3fa6

Maintainer regression-test results for the shipped fix were not provided, and Bluethroat did not rebuild or test the signed enclave running on secret-4. The remediation description in the article is therefore maintainer-stated, supplemented by a reading of the public v1.27.2 source tag (72c548f39602e87924f69289715ae8d7c8cfa7b4), where the ecall_get_genesis_seed handler at cosmwasm/enclaves/execute/src/registration/offchain.rs:531-538 zeroes the output buffer and returns SGX_ERROR_ECALL_NOT_ALLOWED. The 2026-09-07 v1.27 snapshot recorded in section 9 predates that release and does not describe the final v1.27.2 enclave.

2. Release-package provenance

The following artifacts were downloaded from the public v1.26 release:

ArtifactSHA-256
secretnetwork_1.26.0_MAINNET_goleveldb_amd64_ubuntu-22.04.debbb9baf0ee8d32b7800845771de1224edb42876245f1ae730323481353600a6f6
/usr/lib/libgo_cosmwasm.so7dd4917c2ee3693277bccfd548358631335ddc585eee0af7ff195d35532e95dc
/usr/lib/librust_cosmwasm_enclave.signed.so4fc1489204f63a9dfbec40a48c57fff162851685cf669b6ddf5f1d544c705699

Exported symbols present in the shipped host library:

get_encrypted_genesis_seed
init_node
key_gen
get_network_pubkey
free_rust

3. Hardware platform

Receipt time: 2026-09-06T19:17:06Z. The VM was researcher-controlled and ephemeral.

FieldValue
VM sizeAzure Standard_DC4s_v3
CPUIntel Xeon Platinum 8370C, family 6 model 106 stepping 6
SGXsgx and sgx_lc CPU flags present; /dev/sgx_enclave and /dev/sgx_provision present
ImageCanonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:22.04.202608060
Kernel6.8.0-1064-azure #72~22.04.1-Ubuntu
aesmdactive

SGX runtime packages:

  • az-dcap-client 1.13.1
  • libsgx-dcap-default-qpl 1.27.101.1-jammy1
  • libsgx-dcap-ql 1.27.101.1-jammy1
  • libsgx-dcap-quote-verify 1.27.101.1-jammy1
  • libsgx-urts 2.30.101.1-jammy1
  • sgx-aesm-service 2.30.101.1-jammy1

The DCAP configuration selected Secret's public PCCS for verification collateral and Azure THIM for the PCK certificate.

Sealed inventory after the run at 2026-09-06T19:17:07Z: one 4,096-byte sealed key-manager file keyed to the production MRENCLAVE.

4. Production-enclave export witness

The witness was a C program linked against the shipped libgo_cosmwasm.so. It called key_gen to create the enclave's sealed registration key, loaded two distinct synthetic test seeds through the production init_node path, emitted both seed-derived root pairs, and called get_encrypted_genesis_seed with a host-chosen public key.

This was the unmodified signed production enclave from the v1.26 release. Synthetic inputs allowed the witness to run safely without changing the artifact under test.

Registration key created by the target enclave:

new_node_registration_pk=08cc5937066f1d9c971fbccb812c5bf15355e0f4fff43b10f3e944f59463e10f

Import and export receipt from production MRENCLAVE 2b9a69d6…:

ordinary_init_node_succeeded=true
imported_genesis_exchange_pk=acad50bcc555b51fe0615b00e1b68629c36fa9e746de67287a287cacf78bab70
imported_genesis_io_pk=0fd5f1e7cdfc43d95cc71f6b5e487315e1e9058970de283ef443e0029f052871
imported_current_exchange_pk=b9c6ad49167db30aeeb0f760ceb217d368603523a2ddcf76a26401eccfbc3243
imported_current_io_pk=ad4cdef036f9b82701c4c052c58a36755eff6d388b487d18836153c6a7151c7e
host_chosen_pk=7a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522
exported_genesis_ciphertext=7c1fc4daaaf19158b291e55a7eb2e836e3313374c1430dd27c130258a9a7ab9f4c9e0abdb5c43fb5bf3d7a16eac0b36d
exported_genesis_ciphertext_len=48
RESULT=DISTINCT_TWO_SEED_IMPORTED_NODE_RELEASED_GENESIS_SEED_TO_HOST_KEY

A second enclave process performed no key generation or import. It reopened the same sealed store and returned identical roots and ciphertext:

process_mode=distinct_two_seed_imported_node_resume_without_keygen_or_init_node
imported_genesis_exchange_pk=acad50bcc555b51fe0615b00e1b68629c36fa9e746de67287a287cacf78bab70
exported_genesis_ciphertext=7c1fc4daaaf19158b291e55a7eb2e836e3313374c1430dd27c130258a9a7ab9f4c9e0abdb5c43fb5bf3d7a16eac0b36d
RESULT=DISTINCT_TWO_SEED_IMPORTED_NODE_RESUME_RELEASED_GENESIS_SEED_TO_HOST_KEY

The synthetic genesis and current seeds were intentionally distinct. The receipt therefore shows different genesis and current root pairs, while only the genesis seed was wrapped to the host-chosen key.

5. External-decrypt receipt

The verifier ran outside SGX against the public roots and ciphertext emitted by the production enclave. It emitted only the recovered seed's SHA-256 and derived public roots, never the raw 32-byte seed.

attacker_pk=7a1a4e709bf085ac494aba0469b9b1eda0ab1f78b16aabb79ffeda90623e8522
ciphertext_len=48
recovered_seed_sha256=8a83665f3798727f14f92ad0e6c99fdab08ee731d6cd644c131223fd2f4fed2a
derived_genesis_exchange_pk=acad50bcc555b51fe0615b00e1b68629c36fa9e746de67287a287cacf78bab70
exchange_pk_matches_release=true
derived_genesis_io_pk=0fd5f1e7cdfc43d95cc71f6b5e487315e1e9058970de283ef443e0029f052871
io_pk_matches_release=true
wrong_private_key_rejected=true
wrong_associated_data_rejected=true
RESULT=EXACT_RELEASE_GENESIS_SEED_RECOVERED_AND_VERIFIED

The recovered seed hash matched the synthetic genesis fixture (8a83665f…), and both derived public roots matched the enclave's output. A wrong private key and wrong associated data both failed to decrypt.

6. Historical-I/O impact receipt

This software witness implemented the v1.26 seed-to-I/O-key derivation, SecretMessage layout, X25519 shared secret, nonce KDF, and AES-SIV input decryption. It read no network transaction; the seed, plaintext, client key, and nonce were synthetic constants.

fixture_origin=local_synthetic_no_network_secret
public_message_len=141
seed0_io_public_key=73a1bb15ff4cf22cc0e84dacc0f37d1c0eaa42da099ec1a743980d80b518756e
recovered_plaintext={"transfer":{"recipient":"secret1fixture","amount":"123456"}}
wrong_seed_rejected=true
wrong_nonce_rejected=true
RESULT=EXTRACTED_GENESIS_SEED_DECRYPTS_SEED0_TRANSACTION_INPUT

Upstream source anchors at commit 2b8e187…:

  • I/O exchange keypair derivation order 2: crypto/src/consts.rs:65-67
  • Fixed KDF salt and HKDF construction: crypto/src/kdf.rs:6-42
  • Derived value used as an X25519 private key: crypto/src/ed25519.rs:47-74
  • Seed to I/O exchange keypair: utils/src/key_manager.rs:507-510
  • DH output to nonce-derived AES key: contract-engine/src/io.rs:126-132
  • AES-SIV and nonce ‖ pubkey ‖ ciphertext layout: contract-engine/src/types.rs:33-74,96-132
  • AES-128-SIV implementation: crypto/src/aes_siv.rs:25-61

7. Legacy-state impact receipt

This software witness implemented the v1.26 genesis-seed KDF and old contract-state encryption format. It read no mainnet state, sealing file, or live secret.

fixture_origin=local_synthetic_no_network_secret
legacy_stored_value_len=80
recovered_plaintext=historical-secret-contract-state
wrong_seed_rejected=true
wrong_contract_key_rejected=true
tampered_ad_rejected=true
RESULT=EXTRACTED_GENESIS_SEED_DECRYPTS_LEGACY_STATE

Upstream source anchors at commit 2b8e187…:

  • State-IKM derivation order 3: crypto/src/consts.rs
  • Fixed salt and HKDF constructions: crypto/src/kdf.rs
  • generate_consensus_ikm_key: utils/src/key_manager.rs
  • Fallback decryption of old values with consensus_state_ikm.arr[0]: contract-engine/src/db.rs:416-443
  • Migrate-on-write path: contract-engine/src/db.rs:223-267
  • Old producer format: parent of commit 9f8d70bcc

8. Bluethroat's proposed minimal control

This section describes Bluethroat's own control experiment against the vulnerable v1.26 source. It is not Secret's shipped remediation.

The proposal bound the export recipient to the enclave's sealed registration public key. The host did not hold the matching private key, so rejecting every other recipient closed the demonstrated host-chosen-key path.

Control receipt:

base_commit=2b8e18788e70d79245343ed53bc2cc42a345d2d4
patch_applies_to_clean_source=true
policy=only_KEY_MANAGER_registration_key_public_key_is_accepted
attacker_chosen_key=SGX_ERROR_INVALID_PARAMETER

Bluethroat-authored patch, not upstream source:

let registration_public_key = KEY_MANAGER
    .get_registration_key()
    .map_err(|_| sgx_status_t::SGX_ERROR_UNEXPECTED)?
    .get_pubkey();
if target_public_key != registration_public_key {
    warn!("Refusing to export the genesis seed to a non-local registration key");
    return Err(sgx_status_t::SGX_ERROR_INVALID_PARAMETER);
}

This was a minimal compatibility control. Bluethroat's preferred remediation was removal of the unused export surface. Secret separately states that its shipped v1.27 fix refuses a recipient that is not attested. Because the shipped patch and its tests were unavailable, no equivalence between the two controls is claimed.

9. Historical v1.27 source observation

The following observations were captured on 2026-09-07 at public v1.27 commit 91145f355c45fd2464ec486fc800a986cd3b5e2b:

  • cosmwasm/enclaves/execute/Enclave.edl declared ecall_get_genesis_seed public and accepted caller-supplied key bytes.
  • cosmwasm/enclaves/execute/src/registration/offchain.rs checked only len == 32, selected consensus_seed.arr[0], and called encrypt_seed with the caller's key.
  • cosmwasm/enclaves/execute/src/registration/seed_exchange.rs, cosmwasm/packages/sgx-vm/src/attestation.rs, and go-cosmwasm/src/lib.rs were byte-identical to the v1.26 files used for the hardware witness.
  • The ecall_get_genesis_seed function in offchain.rs was unchanged.
  • go-cosmwasm/api/lib.go exposed GetEncryptedGenesisSeed to Go callers.

These are historical pre-release observations. They do not establish the contents or behavior of the final v1.27.2 signed enclave. Signed-binary and hardware evidence in this appendix is limited to the v1.26 artifacts.

10. Safety

No Secret Network consensus seed, mainnet sealed key-manager file, SSH key, Azure credential, or cloud control-plane datum is present in the evidence. Every seed was a synthetic fixture declared in the witness source. The external verifier and both impact witnesses emitted hashes or derived public values instead of raw seed plaintext. The hardware VM, storage paths, and keys were researcher-controlled and ephemeral. No transaction was broadcast to secret-4.