zeroclaw-labs/zeroclaw · error

invalid WeCom padding: empty payload

Error message

invalid WeCom padding: empty payload

What it means

After AES decryption, WeCom payloads are unpadded by reading the last byte as the pad length (strip_wecom_padding, wecom_ws.rs:1864). An empty decrypted payload has no last byte and bails here. In practice the ciphertext decrypted to zero bytes: wrong aes_key or an empty/invalid frame from upstream.

Source

Thrown at crates/zeroclaw-channels/src/wecom_ws.rs:1866

    async fn cancel_draft(&self, _recipient: &str, message_id: &str) -> Result<()> {
        let req_id = self
            .req_id_map
            .lock()
            .remove(message_id)
            .unwrap_or_default();
        if !req_id.is_empty() {
            self.ws_send_respond_msg(&req_id, message_id, "", true)
                .await?;
        }
        Ok(())
    }
}

// ── Helper functions ─────────────────────────────────────────────────

fn strip_wecom_padding(input: &[u8]) -> Result<&[u8]> {
    let Some(last) = input.last() else {
        anyhow::bail!("invalid WeCom padding: empty payload");
    };
    let pad_len = *last as usize;
    if pad_len == 0 || pad_len > 32 || pad_len > input.len() {
        anyhow::bail!("invalid WeCom padding length");
    }
    Ok(&input[..input.len() - pad_len])
}

fn is_wecom_data_version_conflict_error(err: &anyhow::Error) -> bool {
    let msg = err.to_string();
    msg.contains("errcode=6000") || msg.contains("data version conflict")
}

fn parse_inbound_payload(payload: Value) -> Result<ParsedInbound> {
    let msg_type = payload
        .get("msgtype")
        .and_then(Value::as_str)
        .unwrap_or("")

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Verify the aes_key matches the exact WeCom app (43-char EncodingAESKey decoding to 32 bytes)
  2. Log the ciphertext length before decrypt — zero-length input indicts the fetch/upstream, not the key
  3. Skip and dead-letter the message; empty payloads are deterministic and retries will not help
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard the decrypt path: empty payloads are skipped before unpadding
if ciphertext.is_empty() {
    return Ok(None); // nothing to decrypt
}

Try / catch

Catch decrypt/padding errors per message, log msg_id and payload length, and skip that message; if every message fails, treat it as key drift — disable the channel and fix the aes_key rather than skipping forever.

Prevention

When it happens

Trigger: The decrypt path receives an empty input: a mismatched aes_key producing empty plaintext, an upstream empty media body, or a malformed frame that skipped the real ciphertext.

Common situations: aes_key from a different WeCom app or environment (test vs prod); a zero-byte attachment routed through the decrypt path; upstream sending an empty body on error.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/7b058c35234c00ec. Report an issue: GitHub.