{"record":{"id":"ed0b4b866fa2a7ba","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-component-path-is-not-utf-8","errorCode":null,"errorMessage":"durable capsule {id} component path is not UTF-8","messagePattern":"durable capsule (.+?) component path is not UTF-8","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":296,"sourceCode":"    }\n    let expected_imports = crate::wit::version_map_to_strings(&manifest.imports, |definition| {\n        definition.version.to_string()\n    });\n    if metadata.imports != expected_imports {\n        bail!(\"durable capsule {id} imports differ between metadata and archive\");\n    }\n    let expected_exports = crate::wit::version_map_to_strings(&manifest.exports, |definition| {\n        definition.version.to_string()\n    });\n    if metadata.exports != expected_exports {\n        bail!(\"durable capsule {id} exports differ between metadata and archive\");\n    }\n    if authority.wasm_hash_pinned && metadata.wasm_hash != authority.approved_wasm_hash {\n        bail!(\"durable capsule {id} metadata executable hash differs from authority receipt\");\n    }\n    if let Some(component) = manifest.components.first() {\n        let Some(relative) = component.path.to_str() else {\n            bail!(\"durable capsule {id} component path is not UTF-8\");\n        };\n        let Some(bytes) = archive_files.get(relative) else {\n            bail!(\"durable capsule {id} component is missing from its archive\");\n        };\n        if Path::new(relative)\n            .extension()\n            .is_some_and(|extension| extension.eq_ignore_ascii_case(\"wasm\"))\n        {\n            let archive_hash = blake3::hash(bytes).to_hex().to_string();\n            if authority.wasm_hash_pinned\n                && authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())\n            {\n                bail!(\"durable capsule {id} WASM hash differs between authority and archive\");\n            }\n            if metadata.wasm_hash.as_deref() != Some(archive_hash.as_str()) {\n                bail!(\"durable capsule {id} WASM hash differs between metadata and archive\");\n            }\n        } else if metadata.wasm_hash.is_some() {","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L278-L314","documentation":"verify_package_identity reads the first component's path from the manifest and converts it to a UTF-8 string via Path::to_str, because archive entries are keyed by string paths. This bail fires when the component path contains bytes that are not valid UTF-8. The library throws it because archive lookup and downstream path handling require string keys, and non-UTF-8 paths cannot be matched safely against archive entries.","triggerScenarios":"Installing a durable capsule whose CapsuleManifest declares a component with a non-UTF-8 path (e.g. built on a filesystem with raw-byte filenames or a manifest generated by a tool writing arbitrary bytes into the path field), when read_verified_durable_package_for_owner runs identity verification.","commonSituations":"Packages built on legacy/non-UTF-8 locales (e.g. Latin-1 filenames) then moved into archives; hand-crafted or programmatically generated manifests with escaped/mojibake path bytes; tar tools preserving odd encodings on extraction or repack.","solutions":["Rebuild the capsule so the component path in the manifest is valid UTF-8 (ASCII-safe relative path like 'component.wasm').","Fix the manifest's component.path field to a UTF-8 string and repack the archive.","Reject the offending package upstream and regenerate it with normalized (UTF-8) filenames.","Validate every manifest component path with to_str()/String::from_utf8 before packaging to catch this at build time."],"exampleFix":"// before: path from raw OS bytes\nlet path = PathBuf::from(OsString::from_vec(raw_bytes)); // not UTF-8\n// after: normalize to a UTF-8 relative path when building the manifest\nlet path = PathBuf::from(String::from_utf8(raw_bytes).map_err(|_| \"non-UTF-8 component path\")?);","handlingStrategy":"validation","validationCode":"fn assert_utf8_component_paths(manifest: &CapsuleManifest) -> anyhow::Result<()> {\n    for component in &manifest.components {\n        component.path.to_str().ok_or_else(|| anyhow!(\"component path {:?} is not UTF-8\", component.path))?;\n    }\n    Ok(())\n}","typeGuard":"fn has_utf8_path(p: &std::path::Path) -> bool { p.to_str().is_some() }","tryCatchPattern":null,"preventionTips":["Build packages in UTF-8 locales and restrict component filenames to ASCII.","Validate manifest paths at packaging time, not just install time.","Avoid passing raw OsString filenames into manifest generation."],"tags":["utf-8","manifest-validation","path-encoding","rust"],"backgroundTag":"invalid-argument-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}