Hmbown/CodeWhale · error

Codewhale-owned OAuth path escaped the credentials directory

Error message

Codewhale-owned OAuth path escaped the credentials directory

What it means

get_owned_credentials_at validates that the requested credential file's parent directory is exactly the Codewhale xAI OAuth credentials directory (xai_oauth_credentials_dir). Any other parent — including subdirectories or sibling paths — is rejected as a path-traversal guard for Codewhale-owned storage.

Solutions

  1. Use the library's own storage APIs (store.path_for / xai_oauth_credentials_dir) instead of constructing paths manually.
  2. Remove any ".." components or custom directories from the requested path.
  3. Check that the credentials directory isn't a symlink pointing outside the expected location.

Example fix

// before
let path = PathBuf::from("~/.codewhale/other/xai.json");
// after
let dir = codewhale_config::xai_oauth_credentials_dir()?;
let path = dir.join(provider.legacy_file_name());
Defensive patterns

Strategy: validation

Validate before calling

if (path.dirname(requested) !== codewhale.xaiOauthCredentialsDir()) throw new Error('use store.path_for');

Type guard

const inOwnedDir = (p, dir) => path.dirname(path.resolve(p)) === path.resolve(dir);

Try / catch

try { loadOwnedAt(p); } catch (e) { if (String(e).includes('escaped the credentials directory')) loadOwnedViaStore(); }

Prevention

When it happens

Trigger: Calling get_owned_credentials_at(provider, path) where path.parent() != xai_oauth_credentials_dir(), e.g. a path built from user input, "../", or a custom location.

Common situations: Scripting the credentials path with an env override or relative path; migrating an owned file manually to another directory; symlinked credentials directory resolving elsewhere.

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/6b1eba7a7b7d3551. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/oauth.rs:2200

        bail!(
            "Codewhale-owned {} OAuth credentials are not configured",
            oauth_provider_params(provider).display_name
        );
    };
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .context("Codewhale-owned OAuth path must have a UTF-8 basename")?;
    codewhale_config::with_xai_oauth_lifecycle_lock(|store| {
        get_owned_credentials_locked(provider, store, name, |issuer, client_id, refresh| {
            refresh_for_provider(provider, client, issuer, client_id, refresh)
        })
    })
}

fn get_owned_credentials_at(provider: OAuthProvider, path: &Path) -> Result<OwnedOAuthCredentials> {
    let directory = codewhale_config::xai_oauth_credentials_dir()?;
    anyhow::ensure!(
        path.parent() == Some(directory.as_path()),
        "Codewhale-owned OAuth path escaped the credentials directory"
    );
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .context("Codewhale-owned OAuth path must have a UTF-8 basename")?;
    anyhow::ensure!(
        name == provider.legacy_file_name() || provider.is_valid_generation(name),
        "Codewhale-owned OAuth path has an invalid basename"
    );
    codewhale_config::with_xai_oauth_lifecycle_lock(|store| {
        get_owned_credentials_locked(provider, store, name, |issuer, client_id, refresh| {
            refresh_for_provider(
                provider,
                &ReqwestOAuthFormClient,
                issuer,
                client_id,

View on GitHub (pinned to 73e0f67d83)