nautechsystems/nautilus_trader · critical

Protected execution transaction {} contains plaintext

Error message

Protected execution transaction {} contains plaintext

What it means

For payloads expected to be sealed (encrypted), the code enforces that `raw_transaction` is None — an unprotected plaintext transaction alongside sealed storage means confidentiality was violated. The library refuses to process a transaction that stores both a sealed envelope and a plaintext copy. This protects against accidentally broadcasting or logging sensitive signed transactions.

Source

Thrown at crates/adapters/blockchain/src/execution/client.rs:3624

        current.next().is_none(),
        "Execution intent {intent_id} has more than one current hash"
    );
    Ok(row)
}

fn open_execution_payload(
    keys: &PayloadKeySet,
    policy: PayloadPolicy,
    intent: &ExecutionIntentRow,
    hash: &ExecutionTransactionHashRow,
    reason: &str,
) -> anyhow::Result<Vec<u8>> {
    anyhow::ensure!(
        hash.payload_expected,
        "Execution transaction {} has no signed payload",
        hash.transaction_hash
    );
    anyhow::ensure!(
        hash.raw_transaction.is_none(),
        "Protected execution transaction {} contains plaintext",
        hash.transaction_hash
    );
    let envelope = hash.sealed_transaction.as_deref().ok_or_else(|| {
        anyhow::anyhow!(
            "Protected execution transaction {} has no sealed payload",
            hash.transaction_hash
        )
    })?;
    let context = payload_context(intent, hash, keys.deployment_id())?;
    let raw_transaction = keys.unseal(envelope, &context)?;
    log::info!(
        "Unsealed execution payload for intent {} transaction {} during {reason}",
        intent.id,
        hash.transaction_hash
    );
    authenticate_retained_payload(&raw_transaction, intent, hash, keys.deployment_id())?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Remove the plaintext raw_transaction from the row (null it) and rely solely on the sealed envelope.
  2. Find and fix the code path that populated raw_transaction under a sealed payload policy.
  3. Verify the deployment's payload policy/environment is consistently sealed mode end to end.
  4. Rotate any secrets that may have been exposed via the plaintext payload, and audit logs for leaked transactions.

Example fix

// before
row.raw_transaction = Some(raw_signed_tx);
row.sealed_transaction = Some(seal(raw_signed_tx, &context)?);

// after: never keep both
row.sealed_transaction = Some(seal(raw_signed_tx, &context)?);
row.raw_transaction = None;
Defensive patterns

Strategy: validation

Validate before calling

if hash.sealed_transaction.is_some() && hash.raw_transaction.is_some() {
    anyhow::bail!("tx {} stores plaintext alongside sealed envelope", hash.transaction_hash);
}

Type guard

fn is_protected_clean(hash: &ExecutionTransactionHashRow) -> bool {
    hash.sealed_transaction.is_some() && hash.raw_transaction.is_none()
}

Prevention

When it happens

Trigger: Calling the payload-resolution function with a hash row where the protection policy requires sealing but `raw_transaction` is populated with plaintext bytes.

Common situations: A code path that writes raw_transaction for debug/testing and forgot to null it; an environment or policy downgrade (sealed mode intended but sealing disabled); a migration that copied raw payloads into a sealed deployment; mixed-version writers where an older component still stores plaintext.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/47420a1bd27c1c29. Report an issue: GitHub.