{"record":{"id":"0a7a1137bc1fa432","repo":"Hmbown/CodeWhale","slug":"config-path-cannot-contain-components","errorCode":null,"errorMessage":"config path cannot contain '..' components","messagePattern":"config path cannot contain '\\.\\.' components","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":6763,"sourceCode":"                    redact_toml_value_for_display_inner(&path, sensitive, child_value),\n                );\n            }\n            toml::Value::Table(redacted)\n        }\n        _ if sensitive => toml::Value::String(\"********\".to_string()),\n        _ => value.clone(),\n    }\n}\n\nfn normalize_config_file_path(path: PathBuf) -> Result<PathBuf> {\n    if path.as_os_str().is_empty() {\n        bail!(\"config path cannot be empty\");\n    }\n    if path\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\"config path cannot contain '..' components\");\n    }\n    if path.file_name().is_none() {\n        bail!(\"config path must include a file name\");\n    }\n    let absolute = if path.is_absolute() {\n        path\n    } else {\n        std::env::current_dir()\n            .context(\"failed to resolve current directory for config path\")?\n            .join(path)\n    };\n    let file_name = absolute\n        .file_name()\n        .map(OsString::from)\n        .context(\"config path must include a file name\")?;\n    let parent = absolute\n        .parent()\n        .context(\"config path must include a parent directory\")?;","sourceCodeStart":6745,"sourceCodeEnd":6781,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/lib.rs#L6745-L6781","documentation":"normalize_config_file_path rejects config paths containing '..' components before they are resolved. This prevents path traversal out of the intended config directory and avoids ambiguous resolution. It is a deliberate security/validation guard, not a bug.","triggerScenarios":"Passing a config path such as \"/etc/../../home/user/config.toml\", \"../config.toml\", or \"a/../config.toml\" to the config loader.","commonSituations":"Users expressing 'the config one level up' with relative paths, scripts templating paths with ../ segments, or symlink-following setups where users expect .. to be normalized silently.","solutions":["Use an absolute path with no '..' components","Use a relative path that starts from the intended directory without '..' (e.g. \"codewhale/config.toml\")","Resolve the '..' yourself (e.g. via std::fs::canonicalize) before passing the path"],"exampleFix":"// before\nload_config(PathBuf::from(\"../codewhale/config.toml\"))\n// after\nlet path = std::fs::canonicalize(\"../codewhale/config.toml\")?;\nload_config(path)","handlingStrategy":"validation","validationCode":"fn no_parent_components(p: &std::path::Path) -> bool {\n    use std::path::Component;\n    !p.components().any(|c| matches!(c, Component::ParentDir))\n}","typeGuard":"fn safe_config_path(p: &str) -> Option<std::path::PathBuf> {\n    use std::path::Component;\n    let path = std::path::PathBuf::from(p);\n    (!path.components().any(|c| matches!(c, Component::ParentDir))).then_some(path)\n}","tryCatchPattern":"match normalize_config_file_path(path) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"'..' components\") => {\n        eprintln!(\"rewrite the path without '..'; got: {}\", path.display());\n        std::process::exit(2);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always express config paths absolutely","Canonicalize user-supplied paths before passing them in","Ban '..' in path-templating scripts"],"tags":["config","path","path-traversal","validation"],"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"}