Hmbown/CodeWhale · error
Codewhale credentials directory must be lexically normalized
Error message
Codewhale credentials directory must be lexically normalized: {} What it means
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.
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
Example fix
// before
let dir = format!("{}/.codewhale/../codewhale-creds", home);
// after
let dir = std::fs::canonicalize(format!("{}/codewhale-creds", home))?; Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Path, Component};
fn is_lexically_normalized(p: &Path) -> bool {
!p.components().any(|c| matches!(c, Component::CurDir | Component::ParentDir))
}
assert!(is_lexically_normalized(&creds_dir), "normalize the credentials dir before use"); Type guard
fn is_lexically_normalized(p: &std::path::Path) -> bool {
!p.components().any(|c| matches!(c, std::path::Component::CurDir | std::path::Component::ParentDir))
} Try / catch
match xai_oauth_credentials_dir() {
Ok(dir) => { /* use dir */ }
Err(e) if e.to_string().contains("lexically normalized") => {
let cleaned = std::fs::canonicalize(raw_path)?;
retry_with(cleaned);
}
Err(e) => return Err(e),
} Prevention
- Always canonicalize paths before storing them in config
- Never build credential paths with string concatenation
- Reject relative segments at config-parse time
When it happens
Trigger: 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`.
Common situations: 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.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- config path cannot be empty
- config path cannot contain '..' components
- config path must include a file name
- managed directory path cannot be empty
- persistent path allow rules must stay within the workspace
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/38844e5136356d7e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/xai_credentials.rs:131
}
/// Make an owned path absolute without resolving any filesystem component.
/// Canonicalization is deliberately forbidden here: following an existing
/// `credentials` symlink would erase the lexical Codewhale-owned boundary and
/// turn an external directory into an apparently valid destination.
fn lexical_absolute_path(path: &Path) -> Result<PathBuf> {
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.context("resolving the Codewhale credentials directory")?
.join(path)
};
if absolute
.components()
.any(|component| matches!(component, Component::CurDir | Component::ParentDir))
{
bail!(
"Codewhale credentials directory must be lexically normalized: {}",
crate::quote_os_path(&absolute)
);
}
Ok(absolute)
}
pub fn xai_oauth_generation_path(generation: &str) -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(validate_xai_oauth_generation(generation)?))
}
pub fn legacy_xai_oauth_path() -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))
}
pub fn chatgpt_oauth_generation_path(generation: &str) -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(validate_chatgpt_oauth_generation(generation)?))
}View on GitHub (pinned to 73e0f67d83)