{"record":{"id":"7ab38033d16c7e3f","repo":"zeroclaw-labs/zeroclaw","slug":"wecom-media-aeskey-too-short-expected-32-bytes","errorCode":null,"errorMessage":"WeCom media aeskey too short: expected >= 32 bytes, got {}","messagePattern":"WeCom media aeskey too short: expected >= 32 bytes, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wecom_ws.rs","lineNumber":264,"sourceCode":"    }\n}\n\n// ── MediaDecryptor (per-attachment AES key) ──────────────────────────\n\nstruct MediaDecryptor;\n\nimpl MediaDecryptor {\n    /// Decrypt WeCom media attachment using per-message AES key.\n    /// AES-256-CBC, IV = first 16 bytes of key, WeCom-style PKCS padding.\n    fn decrypt(aeskey_b64: &str, encrypted: &[u8]) -> Result<Vec<u8>> {\n        let raw_key = base64::engine::general_purpose::STANDARD\n            .decode(aeskey_b64.trim())\n            .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(aeskey_b64.trim()))\n            .or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(aeskey_b64.trim()))\n            .context(\"failed to decode WeCom media aeskey\")?;\n\n        if raw_key.len() < 32 {\n            anyhow::bail!(\n                \"WeCom media aeskey too short: expected >= 32 bytes, got {}\",\n                raw_key.len()\n            );\n        }\n\n        let key = &raw_key[..32];\n        let iv = &key[..16];\n\n        let mut buf = encrypted.to_vec();\n        let plaintext = cbc::Decryptor::<Aes256>::new(key.into(), iv.into())\n            .decrypt_padded_mut::<NoPadding>(&mut buf)\n            .map_err(|e| {\n                anyhow::Error::msg(format!(\"failed to decrypt WeCom media attachment: {e}\"))\n            })?;\n        Ok(strip_wecom_padding(plaintext)?.to_vec())\n    }\n}\n","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wecom_ws.rs#L246-L282","documentation":"WeCom media/message decryption needs an AES-256 key. The channel base64-decodes the configured aes_key (trying standard, standard-no-pad, and URL-safe alphabets) and requires at least 32 decoded bytes, using the first 32. Anything shorter is a fatal configuration error — a correct WeCom EncodingAESKey is 43 base64 characters decoding to exactly 32 bytes.","triggerScenarios":"Creating/connecting the wecom_ws channel with an aes_key that decodes to fewer than 32 bytes: a truncated key, a value encoded with a wrong scheme, or a placeholder string.","commonSituations":"Copy-paste dropping trailing characters; the EncodingAESKey copied from a different WeCom app (keys are app-specific); the raw 32-byte key stored as-is instead of its 43-char base64 form; env-var interpolation producing an empty or shortened string.","solutions":["Re-copy the 43-character EncodingAESKey from the WeCom app admin page — it decodes to exactly 32 bytes","Check the config/env value for truncation, whitespace, or shell-quoting damage","Verify offline: decode the value with base64; the result must be at least 32 bytes","Reload the channel after fixing the key"],"exampleFix":"# before\naes_key = \"EqQJ1F6cG0uXb2Zt\"            # truncated\n# after\naes_key = \"EqQJ1F6cG0uXb2Zt9wDhN3sRlOaYpKcVeTiBmQ4fXwg\"  # 43-char EncodingAESKey","handlingStrategy":"validation","validationCode":"use base64::Engine;\nfn valid_wecom_aeskey(key: &str) -> bool {\n    let k = key.trim();\n    [\n        &base64::engine::general_purpose::STANDARD,\n        &base64::engine::general_purpose::STANDARD_NO_PAD,\n        &base64::engine::general_purpose::URL_SAFE,\n    ]\n    .iter()\n    .any(|e| e.decode(k).map(|raw| raw.len() >= 32).unwrap_or(false))\n}\nassert!(valid_wecom_aeskey(&cfg.aes_key), \"aes_key must decode to >= 32 bytes\");","typeGuard":null,"tryCatchPattern":"Treat this as a fatal config error: catch it at channel startup, log 'aes_key invalid for app <id>', and keep the channel disabled rather than decrypting garbage.","preventionTips":["Validate the aes_key at config load (43 chars / 32 decoded bytes), not on first media","Store keys in a secret manager rather than inline files prone to truncation","Remember the EncodingAESKey is app-specific: rotate it together with the app's token when credentials change"],"tags":["wecom","aes","encryption","config","base64","key"],"backgroundTag":"invalid-encryption-key","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}