{"record":{"id":"35000472afa9d053","repo":"Hmbown/CodeWhale","slug":"workspace-identity-must-be-a-lowercase-sha-256-hash","errorCode":null,"errorMessage":"workspace identity must be a lowercase SHA-256 hash","messagePattern":"workspace identity must be a lowercase SHA-256 hash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/native_memory.rs","lineNumber":620,"sourceCode":"                match store.get(&access, &m.id) {\n                    Ok(current) => {\n                        store.forget(&access, &current.id, current.revision)?;\n                    }\n                    Err(codewhale_memory::Error::NotFound) => {}\n                    Err(e) => return Err(e.into()),\n                }\n            }\n        }\n        Ok(())\n    }\n}\nfn safe_component(value: &str) -> Result<()> {\n    if value.len() != 64\n        || !value\n            .bytes()\n            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())\n    {\n        bail!(\"workspace identity must be a lowercase SHA-256 hash\");\n    }\n    Ok(())\n}\n\n/// Compose the user-memory prompt block for the native store resolved from a\n/// memory path. Single seam used by the engine, the TUI system-prompt\n/// builder, and the context report so all three describe the same bytes.\n/// Returns `None` when memory is disabled, the path is not a native\n/// `memory/global/MEMORY.md` layout, or there is nothing worth injecting.\n/// The block is a `codewhale.memory.context.v1` envelope: memory entries are\n/// untrusted evidence, never instructions.\n#[must_use]\npub fn native_prompt_block(enabled: bool, memory_path: &Path, workspace: &Path) -> Option<String> {\n    if !enabled {\n        return None;\n    }\n    NativeMemoryStore::from_global_path(memory_path)?\n        .prompt_block(workspace, 32, 12_000)","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/native_memory.rs#L602-L638","documentation":"safe_component validates that a value used as a filesystem path component in the memory store is exactly 64 lowercase hex characters, i.e. a lowercase SHA-256 hash. Workspace identities are hashed before being embedded in directory names, so anything else is rejected to prevent path injection and ambiguous layout.","triggerScenarios":"Calling workspace_scope (or anything else routing through safe_component) with a raw workspace name/path instead of its 64-char lowercase hex SHA-256, or with an uppercase-hex hash.","commonSituations":"Passing a project directory path or workspace name directly instead of hashing it; using an uppercase digest from a different hashing tool; truncating or padding the hash.","solutions":["Hash the workspace identity with SHA-256 and lowercase-hex-encode it before passing it in.","If you already have a hash, lowercase it and verify it is exactly 64 hex characters.","Use the library's own workspace-scope derivation helper rather than constructing the identifier manually."],"exampleFix":"// before\nlet scope = workspace_scope(\"/home/me/project\")?; // rejected\n// after\nlet digest = Sha256::digest(\"/home/me/project\");\nlet scope = workspace_scope(hex::encode(digest))?; // 64 lowercase hex chars","handlingStrategy":"type-guard","validationCode":"fn is_sha256_lower_hex(s: &str) -> bool {\n    s.len() == 64 && s.bytes().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())\n}","typeGuard":"fn valid_workspace_id(id: &str) -> Option<&str> {\n    (id.len() == 64 && id.bytes().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())).then_some(id)\n}","tryCatchPattern":null,"preventionTips":["Always derive workspace identities via SHA-256 lowercase hex, never raw names or paths.","Keep a helper that hashes workspace identifiers at the boundary.","Lowercase any digest produced by external tools before use.","Add a unit check that generated workspace ids are 64 lowercase hex chars."],"tags":["validation","hash","identifier","memory"],"backgroundTag":"invalid-identifier-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}