{"record":{"id":"d008072916fafb4b","repo":"Hmbown/CodeWhale","slug":"state-subdir-must-not-contain-a-root-or-prefix-subdir","errorCode":null,"errorMessage":"state subdir must not contain a root or prefix: {subdir}","messagePattern":"state subdir must not contain a root or prefix: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":5735,"sourceCode":"/// 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\n/// Resolve a state subdirectory, preferring the CodeWhale root if\n/// it already exists, otherwise falling back to the legacy root.\n///\n/// This is the read-path resolver: it returns the primary path when\n/// migration has occurred or on a fresh install, but keeps reading\n/// from the legacy path for users who haven't migrated yet.\npub fn resolve_state_dir(subdir: &str) -> Result<PathBuf> {\n    ensure_safe_state_subdir(subdir)?;","sourceCodeStart":5717,"sourceCodeEnd":5753,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/lib.rs#L5717-L5753","documentation":"Beyond plain absoluteness, a subdir containing a root (`/`) or platform prefix (`C:\\`, UNC) component could redirect resolution outside the state root. `ensure_safe_state_subdir` inspects `Path::components()` and rejects any `RootDir` or `Prefix` component.","triggerScenarios":"Calling a state-path helper with a subdir that parses into `Component::RootDir` or `Component::Prefix(_)` — e.g. `\"/sessions\"`, `\"C:sessions\"`, or `\"\\\\\\\\server\\\\share\"`.","commonSituations":"Windows paths built with drive letters leaking into the subdir slot; strings that begin with a separator; paths copied verbatim from filesystem browsing UIs.","solutions":["Pass a plain relative single component like `\"sessions\"`","Normalize the input with a relative-path join against the state root before calling","Strip leading separators and drive prefixes, or reject the input in your own validation first"],"exampleFix":"// before\nlet dir = state_dir_in(\"/sessions\")?;\n// after\nlet dir = state_dir_in(\"sessions\")?;","handlingStrategy":"validation","validationCode":"use std::path::{Component, Path};\nfn no_root_or_prefix(subdir: &str) -> bool {\n    !Path::new(subdir).components()\n        .any(|c| matches!(c, Component::RootDir | Component::Prefix(_)))\n}","typeGuard":null,"tryCatchPattern":"match state_dir_in(subdir) {\n    Err(e) if e.to_string().contains(\"root or prefix\") => {\n        // strip leading separator / drive prefix and retry\n    }\n    result => result,\n}","preventionTips":["Sanitize platform-specific path components before use","Test subdir inputs on Windows where prefixes are common","Prefer string constants for subdir values over user input"],"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"}