{"record":{"id":"f5a611ae96208be0","repo":"jdx/mise","slug":"invalid-blob-digest-expected-64-lowercase-hex-cha","errorCode":null,"errorMessage":"invalid blob digest (expected 64 lowercase hex chars): {digest}","messagePattern":"invalid blob digest \\(expected 64 lowercase hex chars\\): (.+?)","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/layout.rs","lineNumber":147,"sourceCode":"        let bytes = serde_json::to_vec(manifest)?;\n        self.write_blob(&bytes)\n    }\n}\n\n/// Validate that `digest` is a well-formed `sha256:<64 lowercase hex>` string.\n/// Guards against path traversal from a malicious registry returning something\n/// like `sha256:../../etc/passwd` as a layer digest — without this check, that\n/// would be used directly as a filesystem path component.\npub(crate) fn validate_sha256_digest(digest: &str) -> Result<()> {\n    let Some(hex) = digest.strip_prefix(\"sha256:\") else {\n        eyre::bail!(\"invalid blob digest (expected sha256: prefix): {digest}\");\n    };\n    if hex.len() != 64\n        || !hex\n            .chars()\n            .all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c))\n    {\n        eyre::bail!(\"invalid blob digest (expected 64 lowercase hex chars): {digest}\");\n    }\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn rejects_path_traversal() {\n        assert!(validate_sha256_digest(\"sha256:../../etc/passwd\").is_err());\n        assert!(validate_sha256_digest(\"sha256:../foo\").is_err());\n        assert!(validate_sha256_digest(\"../bad\").is_err());\n        assert!(validate_sha256_digest(\"sha256:DEADBEEF\").is_err());\n    }\n\n    #[test]\n    fn accepts_valid_digest() {","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/layout.rs#L129-L165","documentation":"The second half of validate_sha256_digest: after stripping 'sha256:', the remainder must be exactly 64 characters of lowercase hex ([0-9a-f]). Wrong length, uppercase hex, or non-hex characters (including '../' traversal payloads) all fail here. The strict charset is what makes the digest safe to use as a single filesystem path component.","triggerScenarios":"Digests like 'sha256:ABCDEF...' (uppercase), 'sha256:abc' (short), 'sha256:../../etc/passwd' or 'sha256:../foo' (traversal — covered by the unit test rejects_path_traversal), or digests with whitespace/newlines from sloppy string handling.","commonSituations":"A malicious or broken registry returning crafted digest strings; uppercase digests copied from docs or generated by tools that uppercase hex; truncated digests from manual copying.","solutions":["Use the digest verbatim from the OCI descriptor produced by a conformant registry/tool.","If generating digests yourself, lowercase the hex and ensure 64 chars: format!(\"sha256:{:x}\", Sha256::digest(bytes)).","Treat any digest failing this check in untrusted input as tampering and reject the whole manifest rather than retrying."],"exampleFix":"// before\nlet d = format!(\"sha256:{:X}\", hash); // uppercase hex\n\n// after\nlet d = format!(\"sha256:{:x}\", hash); // 64 lowercase hex chars","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn is_sha256_digest(s: &str) -> bool {\n    let Some(hex) = s.strip_prefix(\"sha256:\") else { return false };\n    hex.len() == 64 && hex.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))\n}   // rejects 'sha256:../foo', uppercase, short/long hex","tryCatchPattern":null,"preventionTips":["When computing digests, use lowercase hex formatting ({:x}, not {:X}).","Treat traversal-shaped digests from remote registries as tampering; abort the pull, do not sanitize and continue.","Cover the guard with the same unit cases mise uses (../../etc/passwd, ../foo)."],"tags":["oci","digest","validation","path-traversal"],"backgroundTag":"oci-digest-validation","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}