Hmbown/CodeWhale · error · anyhow::Error
external credential path escapes its absolute root: {}
Error message
external credential path escapes its absolute root: {} What it means
External-credential consent paths are normalized lexically (only '.'/'..' components, no canonicalize, so no symlink blessing and no I/O before consent) inside resolve_external_credential_path(). If a ParentDir ('..') component pops past the front of the accumulated absolute path, the path would escape its absolute root and normalization bails rather than producing a path outside the granted scope.
Source
Thrown at crates/config/src/external_credentials.rs:129
} else {
std::env::current_dir()
.map_err(|err| anyhow::anyhow!("resolving external credential path: {err}"))?
.join(path)
};
// Normalize only lexical `.` / `..` components. Canonicalization would
// inspect a credential path before consent exists and would also silently
// bless a symlink target. The secure reader rejects symlink/reparse-point
// components when the granted capability is actually consumed.
let mut normalized = PathBuf::new();
for component in absolute.components() {
match component {
Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),
Component::RootDir => normalized.push(component.as_os_str()),
Component::CurDir => {}
Component::ParentDir => {
if !normalized.pop() {
bail!(
"external credential path escapes its absolute root: {}",
quote_os_path(&absolute)
);
}
}
Component::Normal(part) => normalized.push(part),
}
}
if !normalized.is_absolute() {
bail!(
"external credential path must resolve to an absolute path: {}",
quote_os_path(&normalized)
);
}
Ok(normalized)
}
/// The side-effect envelope Codewhale may use for an external credential.View on GitHub (pinned to 0c42157ee5)
Solutions
- Fix the consented path so it stays inside its root after removing '.'/'..' components
- Re-create the consent with the real absolute file location of the external credential
Example fix
# before /path/to/keys/../../other/creds.env # escapes during normalization # after /other/creds.env # or the direct absolute path with no '..' segments
Defensive patterns
Strategy: validation
Validate before calling
// Reject traversal before creating/using a consent:
fn lexically_within_root(path: &std::path::Path) -> bool {
let mut norm = std::path::PathBuf::new();
for c in path.components() {
match c {
std::path::Component::ParentDir => if !norm.pop() { return false },
std::path::Component::CurDir => {}
other => norm.push(other.as_os_str()),
}
}
norm.is_absolute()
} Type guard
fn is_traversal_free(absolute: &Path) -> bool {
let mut depth = 0usize;
for c in absolute.components() {
match c {
Component::Normal(_) => depth += 1,
Component::ParentDir => { if depth == 0 { return false } depth -= 1 }
_ => {}
}
}
true
} Try / catch
match resolve_external_credential_path(&p) {
Ok(normalized) => { /* use normalized */ }
Err(e) if e.to_string().contains("escapes its absolute root") => {
// input error in consent data: reject the consent, never coerce
reject_consent_input(e)
}
Err(e) => return Err(e),
} Prevention
- Always store consent paths fully expanded with no '..' components
- Build credential paths via Path::join on absolute bases instead of string concatenation
- Treat traversal-shaped paths as hostile input, not as fixable at read time
When it happens
Trigger: A consented external credential path whose lexical normalization walks above the root: e.g. '/keys/../../etc/passwd' — after popping '/keys' then '/', another '..' finds nothing left to pop and the bail fires.
Common situations: Hand-crafted or copy-pasted consent paths with too many '..' segments; dotfile-style relative-ish paths pasted into a field that expects an absolute normalized path; probing/misconfiguration where the credential path was meant to point inside a directory tree.
Related errors
- external credential consent path for {} must be absolute
- external credential path must resolve to an absolute path: {
- external credential access is disabled for {}
- managed external credential access is unsupported for {}; no
- external credential consent for {} uses unsupported version
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/1d3cb44a3147b706.
Report an issue: GitHub.