{"record":{"id":"08abf7015aeab18b","repo":"Hmbown/CodeWhale","slug":"codewhale-owned-oauth-path-has-an-invalid-basename","errorCode":null,"errorMessage":"Codewhale-owned OAuth path has an invalid basename","messagePattern":"Codewhale-owned OAuth path has an invalid basename","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/oauth.rs","lineNumber":2208,"sourceCode":"        .context(\"Codewhale-owned OAuth path must have a UTF-8 basename\")?;\n    codewhale_config::with_xai_oauth_lifecycle_lock(|store| {\n        get_owned_credentials_locked(provider, store, name, |issuer, client_id, refresh| {\n            refresh_for_provider(provider, client, issuer, client_id, refresh)\n        })\n    })\n}\n\nfn get_owned_credentials_at(provider: OAuthProvider, path: &Path) -> Result<OwnedOAuthCredentials> {\n    let directory = codewhale_config::xai_oauth_credentials_dir()?;\n    anyhow::ensure!(\n        path.parent() == Some(directory.as_path()),\n        \"Codewhale-owned OAuth path escaped the credentials directory\"\n    );\n    let name = path\n        .file_name()\n        .and_then(|name| name.to_str())\n        .context(\"Codewhale-owned OAuth path must have a UTF-8 basename\")?;\n    anyhow::ensure!(\n        name == provider.legacy_file_name() || provider.is_valid_generation(name),\n        \"Codewhale-owned OAuth path has an invalid basename\"\n    );\n    codewhale_config::with_xai_oauth_lifecycle_lock(|store| {\n        get_owned_credentials_locked(provider, store, name, |issuer, client_id, refresh| {\n            refresh_for_provider(\n                provider,\n                &ReqwestOAuthFormClient,\n                issuer,\n                client_id,\n                refresh,\n            )\n        })\n    })\n}\n\nfn get_owned_credentials_locked<F>(\n    provider: OAuthProvider,","sourceCodeStart":2190,"sourceCodeEnd":2226,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/oauth.rs#L2190-L2226","documentation":"After confirming the credential path's directory, get_owned_credentials_at requires the file's basename to be either the provider's legacy file name or a valid generation name (provider.is_valid_generation). Any other basename is rejected so only known credential files can be read from the owned store.","triggerScenarios":"Calling get_owned_credentials_at with a file name that is neither provider.legacy_file_name() nor a recognized generation file — e.g. \"xai-old.json\", a backup copy, or a typo'd name.","commonSituations":"Manually copying/renaming credential files for backup and pointing the API at the copy; restoring from a backup under a new name; guessing the file name instead of using path_for.","solutions":["Rename the file back to the provider's canonical (legacy or generation) file name.","Use store.path_for(name) to derive the correct path instead of hardcoding it.","Re-run the provider login to regenerate credentials under the canonical name."],"exampleFix":"// before\nget_owned_credentials_at(p, &dir.join(\"xai-backup.json\"))\n// after\nget_owned_credentials_at(p, &dir.join(p.legacy_file_name()))","handlingStrategy":"validation","validationCode":"const name = path.basename(requested); if (name !== provider.legacyFileName && !provider.isValidGeneration(name)) useCanonicalName(provider);","typeGuard":"const isValidOwnedName = (p, provider) => [path.basename(p)].some(n => n === provider.legacyFileName || provider.isValidGeneration(n));","tryCatchPattern":"try { loadOwnedAt(p); } catch (e) { if (String(e).includes('invalid basename')) loadOwnedAt(dir.join(provider.legacyFileName())); }","preventionTips":["Do not rename or copy credential files to backup names inside the store directory","Store backups outside the credentials directory","Use provider.legacy_file_name()/path_for to build file names"],"tags":["oauth","path-traversal","security"],"backgroundTag":"invalid-identifier-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}