{"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":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":6619,"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":6601,"sourceCodeEnd":6637,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/lib.rs#L6601-L6637","documentation":"Thrown by normalize_config_file_path when the config path contains at least one '..' (ParentDir) component. This is a deliberate path-traversal guard: the resolved config location must be expressible without parent references, so the normalizer rejects the input before resolving it against the current directory.","triggerScenarios":"`--config ../shared/codewhale.toml`, `--config ./releases/../../config.toml`, or any relative path that walks upward. The check runs on the raw components, so the '..' is rejected even when it would lexically cancel out.","commonSituations":"Keeping a shared config in a parent directory; monorepo setups reaching for ../../team-config.toml; attempting to work around the symlink rejection (error 148) by pointing through '..'; scripts building paths via concat(cwd, '/../x').","solutions":["Pass an absolute path with no '..' components, e.g. /home/user/shared/codewhale.toml","Or pass a path relative to the current directory that stays inside it, e.g. config/codewhale.toml","Canonicalize the path in your own shell/script first (realpath) and pass the result, which contains no parent components"],"exampleFix":"# before\ncodewhale --config ../shared/codewhale.toml\n\n# after\ncodewhale --config \"$(realpath ../shared/codewhale.toml)\"","handlingStrategy":"validation","validationCode":"use std::path::{Component, Path};\n\nfn traversal_free(p: &Path) -> bool {\n    !p.components().any(|c| matches!(c, Component::ParentDir))\n}\n\nassert!(traversal_free(Path::new(&flag)), \"config path must not contain '..'\");","typeGuard":"fn is_traversal_free(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}","tryCatchPattern":null,"preventionTips":["Canonicalize config paths (realpath/dunce::canonicalize) before handing them to codewhale","Store canonical absolute config paths in your tooling config instead of relative ones with '..'","Treat any '..' in user-supplied paths as invalid input at parse time"],"tags":["rust","config","path-traversal","security"],"backgroundTag":"path-traversal-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}