tinyhumansai/openhuman · error

encrypted payload too short

Error message

encrypted payload too short

What it means

decrypt_handoff_blob base64-decodes the payload and requires at least 32 bytes (16-byte IV + 16-byte GCM tag) before any ciphertext can exist; anything shorter bails with 'encrypted payload too short'. The blob is produced by the backend's encryptMessageFromString (IV || tag || ciphertext, standard base64), so a short payload means truncation or wrong encoding - it is never a key problem (key length is checked separately).

Solutions

  1. Verify the payload is complete, standard base64 and decodes to at least 32 bytes.
  2. Re-issue the handoff from the source system rather than hand-repairing the string.
  3. Confirm the backend still uses the IV16 + tag16 + ciphertext layout of encryptMessageFromString.
  4. Do not strip characters beyond the leading and trailing whitespace the function already trims.
Defensive patterns

Strategy: validation

Validate before calling

fn handoff_blob_decodes_long_enough(b64: &str) -> bool {
    use base64::Engine;
    base64::engine::general_purpose::STANDARD
        .decode(b64.trim())
        .map(|raw| raw.len() >= 32)
        .unwrap_or(false)
}

Prevention

When it happens

Trigger: The handoff blob was truncated in transit: cut off in a URL query param, clipboard copy, or log line; the payload was re-encoded (hex, base64url, double-encoded) instead of standard base64; an empty or placeholder string was passed.

Common situations: Deep-link or query-param handoff where the blob got trimmed by length limits; pasting blobs through tools that strip characters; a backend change to the wire format.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/6d572a1d96fbb558. Report an issue: GitHub.

Appendix: source

Thrown at src/api/rest.rs:1140

        self.authed_json(
            bearer_jwt,
            Method::DELETE,
            &format!("auth/integrations/{id}"),
            None,
        )
        .await?;
        Ok(())
    }
}

/// AES-256-GCM decrypt compatible with backend `encryptMessageFromString` (IV 16 + tag 16 + ciphertext, base64).
pub fn decrypt_handoff_blob(b64_ciphertext: &str, key_str: &str) -> Result<String> {
    let key = key_bytes_from_string(key_str)?;
    let combined = base64::engine::general_purpose::STANDARD
        .decode(b64_ciphertext.trim())
        .context("base64-decode encrypted payload")?;
    if combined.len() < 32 {
        anyhow::bail!("encrypted payload too short");
    }
    let iv = &combined[0..16];
    let tag = &combined[16..32];
    let ciphertext = &combined[32..];

    // aes-gcm expects ciphertext || tag
    let mut ct_with_tag = Vec::with_capacity(ciphertext.len() + tag.len());
    ct_with_tag.extend_from_slice(ciphertext);
    ct_with_tag.extend_from_slice(tag);

    use aes_gcm::aead::generic_array::typenum::U16;
    use aes_gcm::aead::{Aead, KeyInit};
    use aes_gcm::aes::Aes256;
    use aes_gcm::AesGcm;
    type Aes256Gcm16 = AesGcm<Aes256, U16>;

    let cipher =
        Aes256Gcm16::new_from_slice(&key).map_err(|e| anyhow::anyhow!("invalid AES key: {e}"))?;

View on GitHub (pinned to 7491200858)