{"record":{"id":"c7272c5a1cebc91e","repo":"Hmbown/CodeWhale","slug":"image-base64-is-not-canonical","errorCode":null,"errorMessage":"image {} base64 is not canonical","messagePattern":"image (.+?) base64 is not canonical","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/image_attach.rs","lineNumber":161,"sourceCode":"            if bytes.len() > per_image_limit {\n                bail!(\n                    \"image {} exceeds the {} MiB limit\",\n                    index + 1,\n                    per_image_limit / (1024 * 1024)\n                );\n            }\n            total = total.saturating_add(bytes.len());\n            if total_limit.is_some_and(|limit| total > limit) {\n                bail!(\"images exceed the 5 MiB total limit\");\n            }\n            let attached = encode_image_bytes(&bytes, &format!(\"image {}\", index + 1))?;\n            if image.mime != attached.media_type {\n                bail!(\"image {} MIME does not match its content\", index + 1);\n            }\n            decode_and_guard_image(&bytes)?;\n            // Standard padded base64 is the one replay representation.\n            if STANDARD.encode(&bytes) != image.data_base64 {\n                bail!(\"image {} base64 is not canonical\", index + 1);\n            }\n            Ok(attached.content_block())\n        })\n        .collect()\n}\n\n/// Reuse durable canonical bytes for retry, never reread a path or URL.\npub(crate) fn runtime_images_from_blocks(\n    blocks: &[ContentBlock],\n) -> Result<Vec<RuntimeImageInput>> {\n    let mut images = Vec::new();\n    for block in blocks {\n        if let ContentBlock::ImageUrl { image_url } = block {\n            if image_url.url.len() > MAX_IMAGE_BYTES.div_ceil(3) * 4 + 32 {\n                bail!(\"stored image exceeds the attachment limit\");\n            }\n            let (mime, data) = parse_data_url(&image_url.url)\n                .ok_or_else(|| anyhow::anyhow!(\"stored image requires canonical inline content\"))?;","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/image_attach.rs#L143-L179","documentation":"The library requires the canonical replay representation for image payloads: standard padded base64 (STANDARD alphabet with '=' padding). prepare_images_with_limit re-encodes the decoded bytes and bails if the result differs from the supplied data_base64, so non-canonical encodings (URL-safe alphabet, missing padding, embedded whitespace/newlines, wrong case) never enter durable history and every replay round-trips identically.","triggerScenarios":"prepare_runtime_images or prepare_stored_images receives data_base64 that was produced with URL_SAFE base64, stripped padding, line-wrapped MIME-style base64 (e.g. from PEM or email), or contains whitespace/newlines — anything where STANDARD.encode(STANDARD.decode(input)) != input.","commonSituations":"Base64 produced by a URL-safe encoder (JWT-style, '-','_' chars); base64 with newlines inserted every 76 chars from email/PEM tooling; padding stripped to save bytes; base64 copied from JSON with escaped characters mangled.","solutions":["Re-encode the payload with STANDARD padded base64 (e.g. base64::engine::general_purpose::STANDARD.encode(bytes)) before attaching.","Normalize the input: decode with an accepting engine, re-encode with STANDARD, and pass the canonical string.","Strip whitespace/newlines and add padding if you must accept foreign base64 at your boundary.","Standardize on one base64 engine across the pipeline so producers emit canonical form directly."],"exampleFix":"// before\nlet b64 = URL_SAFE_NO_PAD.encode(&bytes);\nattach(RuntimeImageInput { data_base64: b64, .. })?;\n// after\nlet b64 = STANDARD.encode(&bytes);\nattach(RuntimeImageInput { data_base64: b64, .. })?;","handlingStrategy":"validation","validationCode":"fn canonical_b64(input: &str) -> Option<String> {\n    use base64::Engine;\n    let bytes = base64::engine::general_purpose::STANDARD.decode(input).ok()?;\n    Some(base64::engine::general_purpose::STANDARD.encode(bytes))\n}","typeGuard":"fn is_canonical_b64(input: &str) -> bool {\n    use base64::Engine;\n    base64::engine::general_purpose::STANDARD\n        .decode(input)\n        .map(|b| base64::engine::general_purpose::STANDARD.encode(b) == input)\n        .unwrap_or(false)\n}","tryCatchPattern":"match prepare_runtime_images(&images) {\n    Err(e) if e.to_string().contains(\"base64 is not canonical\") => {\n        eprintln!(\"re-encode with standard padded base64 (no URL-safe, no newlines)\"); }\n    other => other?,\n}","preventionTips":["Use one STANDARD base64 engine everywhere in the pipeline","Reject URL-safe or unpadded base64 at system boundaries","Never wrap base64 in newlines when storing or transmitting it"],"tags":["image","base64","encoding"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}