{"record":{"id":"09fdf86fe7eedb20","repo":"jdx/mise","slug":"invalid-recovery-content-identifier","errorCode":null,"errorMessage":"invalid recovery content identifier","messagePattern":"invalid recovery content identifier","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/recovery.rs","lineNumber":123,"sourceCode":"    }\n    restore(state_dir, path, prior)\n}\n\nfn validate_destination(path: &Path) -> Result<()> {\n    if !path.is_absolute() || path.components().any(|c| matches!(c, Component::ParentDir)) {\n        bail!(\"invalid recovery destination\");\n    }\n    for parent in path.ancestors().skip(1) {\n        if std::fs::symlink_metadata(parent).is_ok_and(|meta| meta.is_symlink()) {\n            bail!(\"a parent directory is now a symlink; left untouched\");\n        }\n    }\n    Ok(())\n}\n\nfn validate_blob_id(hash: &str) -> Result<()> {\n    if hash.len() != 64 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) {\n        bail!(\"invalid recovery content identifier\");\n    }\n    Ok(())\n}\n\npub(super) fn read_blob(state_dir: &Path, blob: &Blob) -> Result<Vec<u8>> {\n    use base64::Engine;\n    validate_blob_id(&blob.sha256)?;\n    let bytes = match &blob.inline {\n        Some(inline) => base64::engine::general_purpose::STANDARD.decode(inline)?,\n        None => {\n            let path = super::journal::blobs_dir_in(state_dir).join(&blob.sha256);\n            let metadata = std::fs::symlink_metadata(&path)?;\n            if !metadata.is_file() || metadata.len() != blob.size {\n                bail!(\"invalid recovery content file\");\n            }\n            std::fs::read(path)?\n        }\n    };","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/recovery.rs#L105-L141","documentation":"validate_blob_id requires a content identifier to be exactly 64 ASCII hex characters (a SHA-256 hex digest). Recovery refuses to use a blob reference that does not look like a valid hash, preventing path injection into the blobs directory. Called by read_blob (when loading preimage content) and discard_except (when deleting sidecar blobs).","triggerScenarios":"read_blob or discard_except encounters a Blob.sha256 that is empty, truncated, longer than 64 chars, or contains non-hex characters — i.e. corrupt, hand-edited, or maliciously crafted journal/pending data.","commonSituations":"A state directory damaged by disk failure or partial write, a journal file edited by hand, or future/older schema versions writing different hash formats.","solutions":["Inspect the journal/pending JSON in the state directory and fix or remove the entry with the malformed hash.","Delete the corrupt pending-operation record and accept the files' current contents (`recover <operation> --keep-current` or manual cleanup).","Restore the state directory from backup if multiple records are corrupted."],"exampleFix":"// before (corrupt blob id)\n\"sha256\": \"abc123\"\n\n// after (full 64-char SHA-256 hex digest)\n\"sha256\": \"ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb\"","handlingStrategy":"validation","validationCode":"fn looks_like_sha256(s: &str) -> bool {\n    s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())\n}\n// assert every blob id in the journal before invoking recovery","typeGuard":null,"tryCatchPattern":"match result { Err(e) if e.to_string().contains(\"invalid recovery content identifier\") => /* journal corrupt; discard the pending record */, other => other? }","preventionTips":["Never hand-edit journal or blob metadata files","Back up the state directory so corrupted records can be restored","Keep the mise version consistent; mixing versions writing different record formats can corrupt state"],"tags":["hash","validation","sha256","corruption"],"backgroundTag":"invalid-identifier-format","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}