{"record":{"id":"38844e5136356d7e","repo":"Hmbown/CodeWhale","slug":"codewhale-credentials-directory-must-be-lexically","errorCode":null,"errorMessage":"Codewhale credentials directory must be lexically normalized: {}","messagePattern":"Codewhale credentials directory must be lexically normalized: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":131,"sourceCode":"}\n\n/// Make an owned path absolute without resolving any filesystem component.\n/// Canonicalization is deliberately forbidden here: following an existing\n/// `credentials` symlink would erase the lexical Codewhale-owned boundary and\n/// turn an external directory into an apparently valid destination.\nfn lexical_absolute_path(path: &Path) -> Result<PathBuf> {\n    let absolute = if path.is_absolute() {\n        path.to_path_buf()\n    } else {\n        std::env::current_dir()\n            .context(\"resolving the Codewhale credentials directory\")?\n            .join(path)\n    };\n    if absolute\n        .components()\n        .any(|component| matches!(component, Component::CurDir | Component::ParentDir))\n    {\n        bail!(\n            \"Codewhale credentials directory must be lexically normalized: {}\",\n            crate::quote_os_path(&absolute)\n        );\n    }\n    Ok(absolute)\n}\n\npub fn xai_oauth_generation_path(generation: &str) -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(validate_xai_oauth_generation(generation)?))\n}\n\npub fn legacy_xai_oauth_path() -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))\n}\n\npub fn chatgpt_oauth_generation_path(generation: &str) -> Result<PathBuf> {\n    Ok(xai_oauth_credentials_dir()?.join(validate_chatgpt_oauth_generation(generation)?))\n}","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L113-L149","documentation":"lexical_absolute_path normalizes the configured Codewhale (xAI) credentials directory to an absolute path and refuses paths that still contain `.` or `..` components, since lexically-normalized paths are required to pin the directory's identity safely. The library throws this to prevent aliasing or symlink-swap attacks that unnormalized relative segments would allow.","triggerScenarios":"Calling xai_oauth_credentials_dir (or config loading that derives the credentials directory) with a path containing `.` or `..` components after joining, e.g. `/home/u/.codewhale/../.codewhale` or `./codewhale-creds`.","commonSituations":"Users building the credentials path by string concatenation or from env vars like HOME/XDG_CONFIG_HOME containing trailing relative segments; scripts constructing paths with `..`; config values copied from other tools that allow relative paths.","solutions":["Normalize the path with std::fs::canonicalize (or clean it lexically with a path-normalization helper) before passing it as the credentials directory","Remove `.`/`..` segments from the config value or env var that supplies the path","Verify no symlinked or relative intermediate components remain after the change"],"exampleFix":"// before\nlet dir = format!(\"{}/.codewhale/../codewhale-creds\", home);\n// after\nlet dir = std::fs::canonicalize(format!(\"{}/codewhale-creds\", home))?;","handlingStrategy":"validation","validationCode":"use std::path::{Path, Component};\nfn is_lexically_normalized(p: &Path) -> bool {\n    !p.components().any(|c| matches!(c, Component::CurDir | Component::ParentDir))\n}\nassert!(is_lexically_normalized(&creds_dir), \"normalize the credentials dir before use\");","typeGuard":"fn is_lexically_normalized(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::CurDir | std::path::Component::ParentDir))\n}","tryCatchPattern":"match xai_oauth_credentials_dir() {\n    Ok(dir) => { /* use dir */ }\n    Err(e) if e.to_string().contains(\"lexically normalized\") => {\n        let cleaned = std::fs::canonicalize(raw_path)?;\n        retry_with(cleaned);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always canonicalize paths before storing them in config","Never build credential paths with string concatenation","Reject relative segments at config-parse time"],"tags":["path","validation","config"],"backgroundTag":"invalid-config-value","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"}