nikivdev/code · error

failed to unseal message

Error message

failed to unseal message

What it means

unseal decrypts a sealed message using XSalsa20Poly1305 with a shared secret derived from the recipient's private key and the claimed sender public key. The AEAD authenticate-then-decrypt step failed, which in practice means the ciphertext was not produced by the matching sender key + recipient key + nonce material — i.e. wrong key, wrong sender, or tampered/truncated ciphertext.

Source

Thrown at src/sealer_crypto.rs:75

    Ok(ciphertext)
}

pub fn unseal(
    sealed_message: &[u8],
    recipient_secret: &str,
    sender_id: &str,
    nonce_material: &[u8],
) -> Result<Vec<u8>> {
    let recipient_secret = decode_secret(recipient_secret)?;
    let sender_public = decode_id(sender_id)?;
    let recipient_key = StaticSecret::from(recipient_secret);
    let sender_key = PublicKey::from(sender_public);
    let shared_secret = recipient_key.diffie_hellman(&sender_key).to_bytes();
    let nonce = derive_nonce(nonce_material);
    let cipher = XSalsa20Poly1305::new(&shared_secret.into());
    let plaintext = cipher
        .decrypt(&nonce.into(), sealed_message)
        .map_err(|_| anyhow::anyhow!("failed to unseal message"))?;
    Ok(plaintext)
}

fn decode_secret(value: &str) -> Result<[u8; 32]> {
    let encoded = value
        .strip_prefix(SECRET_PREFIX)
        .ok_or_else(|| anyhow::anyhow!("invalid sealer secret prefix"))?;
    let bytes = bs58::decode(encoded)
        .into_vec()
        .map_err(|e| anyhow::anyhow!("invalid base58 secret: {e}"))?;
    bytes
        .as_slice()
        .try_into()
        .map_err(|_| anyhow::anyhow!("invalid secret key length"))
}

fn decode_id(value: &str) -> Result<[u8; 32]> {
    let encoded = value

View on GitHub (pinned to a747e741ae)

Solutions

  1. Verify you are using the recipient secret matching the public key the value was sealed to; re-seal for the current sealer id if identities were rotated.
  2. Confirm the sender id embedded alongside the ciphertext matches the secret-holder who sealed it.
  3. Re-obtain or re-seal the original value — the ciphertext cannot be repaired.
  4. Check nonce_material is identical to what seal used (same project/env context).

Example fix

// before: value sealed for old sealer id, unseal with new id fails
let v = unseal(&blob, "sealerSecret_zNewKey...", &old_sender_id, nonce)?;
// after: re-seal the value for the current sealer id, then decrypt
let sealed = seal_project_env_value(...)?; // with current ids
let v = decrypt_project_env_value(...)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: recipient secret and sender id must decode before attempting decryption
fn keys_decode(secret: &str, id: &str) -> bool {
    decode_secret_is_ok(secret) && decode_id_is_ok(id)
}

Type guard

fn looks_like_sealer_id(s: &str) -> bool {
    s.starts_with("sealer_z")
}
fn looks_like_sealer_secret(s: &str) -> bool {
    s.starts_with("sealerSecret_z")
}

Try / catch

match unseal(sealed_message, recipient_secret, sender_id, nonce_material) {
    Err(e) if e.to_string().contains("failed to unseal message") => {
        eprintln!(
            "decryption failed: wrong recipient secret, wrong sender id, or corrupted ciphertext \
             (sealed-for vs current sealer id mismatch?)"
        );
        Err(anyhow::anyhow!("sealed value cannot be decrypted with current identity; re-seal required"))
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling unseal (via decrypt_project_env_value or unseal_private_key) when: the ciphertext was sealed for a different recipient, sealed by a different sender than decode_id resolved, the sealed bytes are truncated/corrupted, or nonce_material differs from what was used at seal time.

Common situations: Rotating sealer identities without re-sealing stored values, pulling env values sealed by a teammate with a different sealer ID, merging files where the sealed blob and the sender id got out of sync, or a partially written/corrupted file.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/436e7cf3e2cbd5f7. Report an issue: GitHub.