{"record":{"id":"0956ef1b98f4e549","repo":"zeroclaw-labs/zeroclaw","slug":"media-aes-key-must-decode-to-16-raw-bytes-or-32-he","errorCode":null,"errorMessage":"media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes","messagePattern":"media aes_key must decode to 16 raw bytes or 32 hex chars, got (.+?) bytes","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wechat.rs","lineNumber":424,"sourceCode":"                \"media nested hex aes_key invalid\"\n            );\n            anyhow::Error::msg(format!(\"media nested hex aes_key invalid: {e}\"))\n        })?;\n        return <[u8; 16]>::try_from(bytes.as_slice()).map_err(|_| {\n            ::zeroclaw_log::record!(\n                WARN,\n                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)\n                    .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                    .with_attrs(\n                        ::serde_json::json!({\"key_kind\": \"nested_hex\", \"expected_bytes\": 16})\n                    ),\n                \"wechat: media nested hex aes_key has wrong byte length\"\n            );\n            anyhow::Error::msg(\"media nested hex aes_key must be 16 bytes\")\n        });\n    }\n\n    anyhow::bail!(\n        \"media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes\",\n        decoded.len()\n    )\n}\n\nfn https_base_url(\n    field_name: &str,\n    value: Option<String>,\n    default: &str,\n) -> anyhow::Result<String> {\n    let url = value.unwrap_or_else(|| default.to_string());\n    let url = url.trim().trim_end_matches('/').to_string();\n    if !url.starts_with(\"https://\") {\n        anyhow::bail!(\"{field_name} must use https://, got {url}\");\n    }\n    Ok(url)\n}\n","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wechat.rs#L406-L442","documentation":"parse_aes_key in the WeChat channel decodes the aes_key attached to an inbound media message before AES-128-ECB decrypting the file. It accepts exactly three shapes: 32 ASCII hex chars; base64 decoding to 16 raw bytes; or base64 of 32 ASCII hex chars (nested hex). This error fires when the base64 decode succeeded but produced a byte length that is neither 16 nor 32-hex — so the key arrived, but in a malformed or unexpected encoding.","triggerScenarios":"download_inbound_attachment receives an event whose aes_key (1) was copied/serialized with extra characters so base64 decodes to 17/24/33... bytes; (2) is base64 of a 16-byte key with padding or whitespace mangled by upstream JSON handling; (3) comes from a WeChat iLink API change that altered key encoding (e.g. raw bytes length different than 16, or a new wrapper). Every length-check branch above logs a WARN with key_kind before this fallthrough bail.","commonSituations":"WeChat backend rolling out a new media-message format while older channel code parses it; a proxy/transformer in the event pipeline re-encoding the key; hand-crafted test payloads using a 24-byte (192-bit) or 32-byte raw key instead of the required 128-bit key; config/tutorial examples showing a placeholder key of the wrong length.","solutions":["Dump the raw aes_key string from the inbound event and check its length: 32 chars hex, or base64 that decodes to exactly 16 bytes.","Confirm the value is taken verbatim from the attachment payload (no quotes, escaping, or trimming artifacts introduced by your pipeline).","If WeChat/iLink changed the encoding shape, update the channel to map the new format to [u8; 16].","For test payloads, generate a real key: openssl rand -hex 16 (32 hex chars) — never paste a raw 24/32-byte string."],"exampleFix":"// before — placeholder key of the wrong length\nlet key = parse_aes_key(\"YWJjZGVmZ2hpamtsbW5vcA==\")?; // base64 -> 16 bytes? no: 17 bytes -> error\n\n// after — supply a genuine 128-bit key in one of the accepted shapes\nlet hex_key = \"5a1f2b3c4d5e6f708192a3b4c5d6e7f8\";      // 32 hex chars\nlet key = parse_aes_key(hex_key)?;                       // Ok([u8; 16])","handlingStrategy":"validation","validationCode":"// Validate an inbound aes_key before attempting the attachment download.\nfn aes_key_shape_ok(raw: &str) -> bool {\n    let t = raw.trim();\n    if t.len() == 32 && t.bytes().all(|b| b.is_ascii_hexdigit()) {\n        return true; // 32 hex chars\n    }\n    match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, t) {\n        Ok(bytes) => bytes.len() == 16\n            || (bytes.len() == 32 && bytes.iter().all(u8::is_ascii_hexdigit)),\n        Err(_) => false,\n    }\n}\nanyhow::ensure!(aes_key_shape_ok(&event.aes_key), \"malformed aes_key: wrong length/encoding\");","typeGuard":null,"tryCatchPattern":"match channel.download_inbound_attachment(&event).await {\n    Ok(bytes) => { /* persist media */ }\n    Err(e) if e.to_string().contains(\"media aes_key must decode to\") => {\n        // Upstream payload problem, not transient: log the key length/encoding,\n        // skip this attachment, and never retry the identical payload.\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Treat the event's aes_key as opaque — pass it through verbatim without trimming/re-escaping beyond the channel's own handling.","Build test fixtures with openssl rand -hex 16 so keys always have a valid shape.","Log key length (never the key) on parse failure to detect upstream format changes quickly.","Pin the WeChat/iLink API behavior in integration tests so an upstream key-encoding change fails loudly in CI."],"tags":["wechat","aes-key","encryption","media","validation"],"backgroundTag":"invalid-encryption-key","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}