Hmbown/CodeWhale · error · anyhow::Error
external credential consent path for {} must be lexically no
Error message
external credential consent path for {} must be lexically normalized: {} What it means
The consent must name its path in already-normalized lexical form: validate_read_scope() re-runs resolve_external_credential_path() on the stored path and requires the result to equal the stored value byte-for-byte. Any '.', '..', or redundant component that normalization would strip makes them differ and the consent is refused — forcing the stored grant to match exactly what will be opened.
Source
Thrown at crates/config/src/external_credentials.rs:442
provider.as_str()
);
}
if self.source != source {
bail!(
"external credential consent source mismatch for {} (expected {})",
provider.as_str(),
source.as_str()
);
}
if !self.path.is_absolute() {
bail!(
"external credential consent path for {} must be absolute",
provider.as_str()
);
}
let normalized = resolve_external_credential_path(&self.path)?;
if normalized != self.path {
bail!(
"external credential consent path for {} must be lexically normalized: {}",
provider.as_str(),
quote_os_path(&self.path)
);
}
if self.path != resolved_path {
bail!(
"external credential path changed for {}; consent covers {}, current path is {}",
provider.as_str(),
quote_os_path(&self.path),
quote_os_path(resolved_path)
);
}
Ok(())
}
/// Validate and mint the read capability consumed by credential adapters.
/// No filesystem operation occurs while validating the policy.View on GitHub (pinned to 0c42157ee5)
Solutions
- Rewrite the consent path with all '.'/'..' components resolved: /keys/acme.env, not /keys/./acme.env
- Re-create the consent from the real canonical location of the file
Example fix
# before path = "/etc/codewhale/../codewhale/keys/acme.env" # after path = "/etc/codewhale/keys/acme.env"
Defensive patterns
Strategy: validation
Validate before calling
// Normalize at consent-creation time so stored == normalized: let normalized = resolve_external_credential_path(&raw_path)?; // same lexical rules // store `normalized` in the consent record, never raw_path
Type guard
fn is_lexically_normalized(p: &Path) -> bool {
p.components().all(|c| !matches!(c, Component::CurDir | Component::ParentDir))
&& resolve_external_credential_path(p).map(|n| n == p).unwrap_or(false)
} Try / catch
match consent.validate_read_scope(provider, source, &resolved) {
Ok(()) => read_external_credential(&resolved),
Err(e) if e.to_string().contains("must be lexically normalized") => {
let clean = resolve_external_credential_path(&consent.path)?; // reconsent with `clean`
reconsent_with_path(provider, source, &clean).await
}
Err(e) => Err(e),
} Prevention
- Create consents only through tooling that stores the normalized path
- Avoid '.'/'..' segments in any stored credential path
- After moving credential files, re-consent with the new direct path rather than writing traversal equivalents
When it happens
Trigger: A consent path like "/keys/../keys/acme.env" or "/keys/./acme.env" (or with a trailing artifact) normalizes to a different value than stored, so normalized != self.path trips the bail.
Common situations: Hand-edited or programmatically joined consent paths containing '.'/'..' segments; paths pasted from shells with redundant components; symlinks in the directory making users write traversal-style equivalents.
Related errors
- external credential path escapes its absolute root: {}
- external credential access is disabled for {}
- managed external credential access is unsupported for {}; no
- external credential consent for {} uses unsupported version
- external credential consent is scoped to provider {:?}, not
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/19a9dc98ada1a2df.
Report an issue: GitHub.