{"record":{"id":"5c6368dde7261282","repo":"Hmbown/CodeWhale","slug":"state-subdir-must-not-be-an-absolute-path-subdir","errorCode":null,"errorMessage":"state subdir must not be an absolute path: {subdir}","messagePattern":"state subdir must not be an absolute path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5727,"sourceCode":"/// Always returns the legacy path regardless of whether it exists.\npub fn legacy_deepseek_home() -> Result<PathBuf> {\n    codewhale_paths::legacy_deepseek_home().context(\"failed to resolve home directory\")\n}\n\n/// Reject state subdirs that could escape the state root via path injection.\n///\n/// `ensure_state_dir` / `resolve_state_dir` are public APIs taking an arbitrary\n/// subdir string; every in-tree caller passes a hardcoded single component\n/// (e.g. `\"sessions\"`, `\".\"`). This validates defensively so a future caller\n/// can never traverse out of the state root via `..` components or an absolute\n/// path. Nested relative paths such as `\"a/b\"` are permitted.\nfn ensure_safe_state_subdir(subdir: &str) -> Result<()> {\n    if subdir.is_empty() {\n        bail!(\"state subdir must not be empty\");\n    }\n    let path = std::path::Path::new(subdir);\n    if path.is_absolute() {\n        bail!(\"state subdir must not be an absolute path: {subdir}\");\n    }\n    if path.components().any(|c| {\n        matches!(\n            c,\n            std::path::Component::RootDir | std::path::Component::Prefix(_)\n        )\n    }) {\n        bail!(\"state subdir must not contain a root or prefix: {subdir}\");\n    }\n    if path\n        .components()\n        .any(|c| matches!(c, std::path::Component::ParentDir))\n    {\n        bail!(\"state subdir must not contain parent-dir (..) components: {subdir}\");\n    }\n    Ok(())\n}\n","sourceCodeStart":5709,"sourceCodeEnd":5745,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/lib.rs#L5709-L5745","documentation":"State subdirectories must be relative to the state root; an absolute subdir (e.g. `/tmp/x`, `C:\\x`) would escape the state root entirely. `ensure_safe_state_subdir` rejects any subdir string that `Path::is_absolute()` accepts.","triggerScenarios":"Calling a state-path helper with `subdir` starting with `/` (Unix) or containing a Windows drive/UNC prefix.","commonSituations":"Users setting a state-dir-like env var to an absolute path and passing it as the subdir; joining a configured absolute path into the subdir slot; copying a full path from another tool.","solutions":["Pass only the relative component (e.g. `\"sessions\"`), not a full path","Convert the absolute path to a path relative to the state root before passing it","Use the API that accepts an explicit state-root override instead of smuggling it via subdir"],"exampleFix":"// before\nlet dir = state_dir_in(\"/home/u/.codewhale/sessions\")?;\n// after\nlet dir = state_dir_in(\"sessions\")?;","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn is_relative_subdir(subdir: &str) -> bool {\n    !subdir.is_empty() && !Path::new(subdir).is_absolute()\n}","typeGuard":"fn safe_subdir(subdir: &str) -> Option<&str> {\n    (!subdir.is_empty() && !Path::new(subdir).is_absolute()).then_some(subdir)\n}","tryCatchPattern":"match state_dir_in(subdir) {\n    Err(e) if e.to_string().contains(\"absolute path\") => {\n        // recompute as path relative to state root and retry\n    }\n    result => result,\n}","preventionTips":["Split configured absolute state dirs into root + relative subdir","Use `Path::strip_prefix` to derive relative components","Never feed full filesystem paths into a subdir parameter"],"tags":["path","validation","state"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}