Hmbown/CodeWhale · error

external credential path escapes its absolute root

Error message

external credential path escapes its absolute root: {}

What it means

Thrown by resolve_external_credential_path while lexically normalizing an absolute credential path: a `..` component would climb above the root, meaning the path escapes its declared absolute root. The library refuses such paths without any filesystem I/O, closing a classic path-traversal hole in external credential lookup.

Solutions

  1. Rewrite the path so it stays under its absolute root, removing excess `..` segments
  2. Point the external credential source at the actual absolute file path
  3. If indirection is needed, move the root up so the relative portion stays inside it
  4. Reject or sanitize the path at configuration time before it reaches read_grant

Example fix

// before
let path = "/secure/creds/../../etc/passwd";
resolve_external_credential_path(path)?;
// after
let path = "/secure/creds/token.txt";
resolve_external_credential_path(path)?;
Defensive patterns

Strategy: validation

Validate before calling

fn stays_under_root(path: &str, root: &str) -> bool {
    std::path::Path::new(root).join(path)
        .components()
        .try_fold(0usize, |depth, c| match c {
            std::path::Component::ParentDir => if depth == 0 { None } else { Some(depth - 1) },
            std::path::Component::Normal(_) => Some(depth + 1),
            _ => Some(depth),
        }).is_some()
}

Prevention

When it happens

Trigger: An ExternalCredentialSource path like /safe/root/../../etc/passwd, or a symlink-free path string containing enough `..` segments to underflow the root (e.g. /../secret) passed to resolve_external_credential_path via validate_read_scope/read_grant.

Common situations: Config entries or tool-supplied paths that embed relative escapes; attackers or buggy scripts composing credential paths from untrusted fragments.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1d3cb44a3147b706. Report an issue: GitHub.

Appendix: 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 73e0f67d83)