{"record":{"id":"e83dd36d6ade926a","repo":"Hmbown/CodeWhale","slug":"runtime-store-root-cannot-contain-components","errorCode":null,"errorMessage":"Runtime store root cannot contain '..' components","messagePattern":"Runtime store root cannot contain '\\.\\.' components","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":8863,"sourceCode":"fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {\n    if let Some(message) = payload.downcast_ref::<&str>() {\n        (*message).to_string()\n    } else if let Some(message) = payload.downcast_ref::<String>() {\n        message.clone()\n    } else {\n        \"unknown panic payload\".to_string()\n    }\n}\n\nfn checked_runtime_store_root(root: PathBuf) -> Result<PathBuf> {\n    if root.as_os_str().is_empty() {\n        bail!(\"Runtime store root cannot be empty\");\n    }\n    if root\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\"Runtime store root cannot contain '..' components\");\n    }\n    let absolute = if root.is_absolute() {\n        root\n    } else {\n        std::env::current_dir()\n            .context(\"failed to resolve current directory for runtime store\")?\n            .join(root)\n    };\n    match absolute.canonicalize() {\n        Ok(path) => Ok(path),\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {\n            Ok(normalize_path_components(&absolute))\n        }\n        Err(err) => Err(err).with_context(|| {\n            format!(\n                \"Failed to resolve runtime store root {}\",\n                absolute.display()\n            )","sourceCodeStart":8845,"sourceCodeEnd":8881,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L8845-L8881","documentation":"checked_runtime_store_root rejects any root containing a ParentDir (\"..\") component (crates/tui/src/runtime_threads.rs:8863). The guard prevents traversal-style and non-canonical roots; relative roots are instead joined onto the current directory and canonicalized, so no \"..\" is ever needed.","triggerScenarios":"Configuring the store root as \"../../shared/state\" or \"data/../store\"; building the path by string concatenation that leaves \"..\" fragments; paths written for one machine layout reused on another","commonSituations":"Shared dotfiles with machine-specific relative escapes; monorepo configs pointing outside the tree with \"..\"; scripts joining user input without normalization.","solutions":["Use an absolute, canonical path for the store root","Resolve the path first (fs::canonicalize, or a normalizing join) so no ParentDir components remain","Point the store root directly at the target directory instead of escaping with \"..\"","Validate configuration paths at startup with the same component check"],"exampleFix":"// before\nlet root = PathBuf::from(\"../../shared/codewhale-store\");\n\n// after\nlet root = fs::canonicalize(\"../../shared/codewhale-store\")\n    .context(\"store root must exist\")?; // absolute, no ParentDir components","handlingStrategy":"validation","validationCode":"use std::path::{Component, Path};\nfn has_parent_component(root: &Path) -> bool {\n    root.components().any(|c| matches!(c, Component::ParentDir))\n}\nlet root = if has_parent_component(&root) {\n    fs::canonicalize(&root).context(\"resolve store root\")? // removes '..'\n} else { root };","typeGuard":"fn is_safe_store_root(root: &Path) -> bool {\n    !root.as_os_str().is_empty()\n        && !root.components().any(|c| matches!(c, Component::ParentDir))\n}","tryCatchPattern":"match RuntimeStore::open(root) {\n    Ok(store) => store,\n    Err(err) if err.to_string().contains(\"cannot contain '..'\") => {\n        let canonical = fs::canonicalize(&root)\n            .context(\"store root must exist to be canonicalized\")?;\n        RuntimeStore::open(canonical)?\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Prefer absolute, canonical store roots in config","Never assemble store paths by raw string concatenation; use Path::join and normalize","Reject '..' in user-supplied paths at the input boundary, not at store open","Validate paths once at startup with the same component rule"],"tags":["rust","configuration","filesystem","path-validation","store"],"backgroundTag":"path-traversal-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}