{"record":{"id":"436e7cf3e2cbd5f7","repo":"nikivdev/code","slug":"failed-to-unseal-message","errorCode":null,"errorMessage":"failed to unseal message","messagePattern":"failed to unseal message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sealer_crypto.rs","lineNumber":75,"sourceCode":"    Ok(ciphertext)\n}\n\npub fn unseal(\n    sealed_message: &[u8],\n    recipient_secret: &str,\n    sender_id: &str,\n    nonce_material: &[u8],\n) -> Result<Vec<u8>> {\n    let recipient_secret = decode_secret(recipient_secret)?;\n    let sender_public = decode_id(sender_id)?;\n    let recipient_key = StaticSecret::from(recipient_secret);\n    let sender_key = PublicKey::from(sender_public);\n    let shared_secret = recipient_key.diffie_hellman(&sender_key).to_bytes();\n    let nonce = derive_nonce(nonce_material);\n    let cipher = XSalsa20Poly1305::new(&shared_secret.into());\n    let plaintext = cipher\n        .decrypt(&nonce.into(), sealed_message)\n        .map_err(|_| anyhow::anyhow!(\"failed to unseal message\"))?;\n    Ok(plaintext)\n}\n\nfn decode_secret(value: &str) -> Result<[u8; 32]> {\n    let encoded = value\n        .strip_prefix(SECRET_PREFIX)\n        .ok_or_else(|| anyhow::anyhow!(\"invalid sealer secret prefix\"))?;\n    let bytes = bs58::decode(encoded)\n        .into_vec()\n        .map_err(|e| anyhow::anyhow!(\"invalid base58 secret: {e}\"))?;\n    bytes\n        .as_slice()\n        .try_into()\n        .map_err(|_| anyhow::anyhow!(\"invalid secret key length\"))\n}\n\nfn decode_id(value: &str) -> Result<[u8; 32]> {\n    let encoded = value","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/sealer_crypto.rs#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Confirm the sender id embedded alongside the ciphertext matches the secret-holder who sealed it.","Re-obtain or re-seal the original value — the ciphertext cannot be repaired.","Check nonce_material is identical to what seal used (same project/env context)."],"exampleFix":"// before: value sealed for old sealer id, unseal with new id fails\nlet v = unseal(&blob, \"sealerSecret_zNewKey...\", &old_sender_id, nonce)?;\n// after: re-seal the value for the current sealer id, then decrypt\nlet sealed = seal_project_env_value(...)?; // with current ids\nlet v = decrypt_project_env_value(...)?;","handlingStrategy":"try-catch","validationCode":"// pre-check: recipient secret and sender id must decode before attempting decryption\nfn keys_decode(secret: &str, id: &str) -> bool {\n    decode_secret_is_ok(secret) && decode_id_is_ok(id)\n}","typeGuard":"fn looks_like_sealer_id(s: &str) -> bool {\n    s.starts_with(\"sealer_z\")\n}\nfn looks_like_sealer_secret(s: &str) -> bool {\n    s.starts_with(\"sealerSecret_z\")\n}","tryCatchPattern":"match unseal(sealed_message, recipient_secret, sender_id, nonce_material) {\n    Err(e) if e.to_string().contains(\"failed to unseal message\") => {\n        eprintln!(\n            \"decryption failed: wrong recipient secret, wrong sender id, or corrupted ciphertext \\\n             (sealed-for vs current sealer id mismatch?)\"\n        );\n        Err(anyhow::anyhow!(\"sealed value cannot be decrypted with current identity; re-seal required\"))\n    }\n    other => other,\n}","preventionTips":["Re-seal all stored values whenever sealer identities are rotated.","Record which sealer id sealed each value alongside the ciphertext.","Verify your sealer id (get_sealer_id on your secret) matches the intended recipient before decrypting.","Treat any unseal failure as data integrity issue: re-fetch the value rather than retrying."],"tags":["crypto","decryption","aead","authentication","key-mismatch"],"backgroundTag":"decryption-failed","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}