{"record":{"id":"694dfa3fa2fbdcf1","repo":"Hmbown/CodeWhale","slug":"invalid-memory-workspace-id","errorCode":null,"errorMessage":"invalid memory workspace id","messagePattern":"invalid memory workspace id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/native_memory.rs","lineNumber":911,"sourceCode":"    Ok(note.trim_start_matches('-').trim().to_string())\n}\n\nfn validate_query(query: &str) -> Result<&str> {\n    let query = query.trim();\n    if query.is_empty() || query.chars().count() > MAX_QUERY_CHARS {\n        bail!(\"memory search query is empty or too long\");\n    }\n    Ok(query)\n}\n\nfn safe_component(value: &str) -> Result<String> {\n    if value.is_empty()\n        || value == \".\"\n        || value == \"..\"\n        || value.contains('/')\n        || value.contains('\\\\')\n    {\n        bail!(\"invalid memory workspace id\");\n    }\n    Ok(value.to_string())\n}\n\nfn ensure_memory_file(path: &Path) -> Result<()> {\n    if let Some(parent) = path.parent() {\n        fs::create_dir_all(parent)?;\n    }\n    if !path.exists() {\n        File::create(path)?;\n    }\n    Ok(())\n}\n\nfn file_mtime(path: &Path) -> Result<i64> {\n    Ok(fs::metadata(path)?\n        .modified()?\n        .duration_since(UNIX_EPOCH)","sourceCodeStart":893,"sourceCodeEnd":929,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/native_memory.rs#L893-L929","documentation":"safe_component sanitizes strings used as filesystem path components (workspace ids). A value that is empty, '.', '..', or contains '/' or '\\' could escape or corrupt the memory directory, so the function refuses it. This is a path-traversal guard applied to every workspace id before it becomes a directory name.","triggerScenarios":"Calling workspace_path (directly or via memory APIs taking a workspace id) with an id that is empty, equals '.' or '..', or embeds a slash or backslash character.","commonSituations":"Workspace ids derived from untrusted or user-supplied names like 'team/sub'; Windows-style paths 'a\\\\b' reaching validation; default/placeholder ids left as '.'; empty id from a missing config field.","solutions":["Sanitize the workspace id: strip or replace path separators and reject '.'/'..' before calling","Derive the id from a safe slug (alphanumeric, dash, underscore) of the original name","Reject the value upstream in config parsing so an invalid id never reaches workspace_path"],"exampleFix":"// before\nlet ws = user_supplied_name; // \"team/sub\"\nmemory.write(ws, note)\n// after\nlet ws = user_supplied_name.replace(['/', '\\\\'], \"-\");\nif ws.is_empty() || ws == \".\" || ws == \"..\" { return Err(\"bad workspace id\"); }\nmemory.write(ws, note)","handlingStrategy":"validation","validationCode":"fn is_safe_component(v: &str) -> bool {\n    !(v.is_empty() || v == \".\" || v == \"..\" || v.contains('/') || v.contains('\\\\'))\n}","typeGuard":"fn safe_workspace_id(v: &str) -> Option<&str> {\n    if v.is_empty() || v == \".\" || v == \"..\" || v.contains('/') || v.contains('\\\\') { None } else { Some(v) }\n}","tryCatchPattern":"match memory.workspace_path(id) {\n    Ok(path) => path,\n    Err(e) if e.to_string().contains(\"invalid memory workspace id\") => {\n        return Err(anyhow!(\"workspace id '{}' contains path separators\", id))\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Slugify user-provided workspace names (allow [A-Za-z0-9_-] only)","Validate workspace ids at config-load time, not just at filesystem use","Never construct workspace ids by joining raw user input with separators"],"tags":["path-traversal","validation","workspace-id"],"backgroundTag":"path-traversal-blocked","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}