Skip to content

Fix Claude plugin auth compatibility across custom OAuth and keychain setups#331

Merged
robinebers merged 3 commits intomainfrom
fix/claude-plugin-auth-compat
Apr 7, 2026
Merged

Fix Claude plugin auth compatibility across custom OAuth and keychain setups#331
robinebers merged 3 commits intomainfrom
fix/claude-plugin-auth-compat

Conversation

@robinebers
Copy link
Copy Markdown
Owner

@robinebers robinebers commented Mar 31, 2026

Summary

  • add Claude auth compatibility for custom config dirs, custom OAuth endpoints, staging/local OAuth, and env-injected access tokens
  • update Claude credential lookup and refresh behavior to use the correct keychain service, pass custom home paths to ccusage, and include the user:file_upload scope
  • expand host API env allowlists and redaction coverage for Claude auth inputs, keychain logging, IDs, and filesystem paths
  • add plugin and Rust regression tests covering the new Claude auth and redaction paths

Testing

  • Not run (not requested)

Note

Medium Risk
Touches credential loading/refresh, keychain access, and environment-variable handling; mistakes could break login/usage probing or leak sensitive values if redaction misses edge cases.

Overview
Improves Claude plugin authentication portability by supporting custom Claude config directories (CLAUDE_CONFIG_DIR), env-injected access tokens (CLAUDE_CODE_OAUTH_TOKEN, treated as inference-only), and dynamic OAuth endpoints/client IDs (staging/local/custom URLs) with per-environment keychain service names.

The plugin now prefers a macOS current-user keychain entry with legacy fallback, writes refreshed tokens back to the same source, passes an optional home path through to ccusage, skips live /api/oauth/usage calls when tokens lack user:profile, and refreshes with the expanded scope including user:file_upload.

On the Tauri/Rust side, the host API expands the env var allowlist for Claude auth inputs, adds current-user keychain read/write bindings, expands redaction to cover filesystem paths and additional IDs, and redacts app_data_dir/ccusage logging. Tests were updated/added across JS and Rust to cover the new keychain/OAuth/redaction behaviors.

Reviewed by Cursor Bugbot for commit 110bee6. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Improves Claude plugin auth to work across custom config dirs and custom/staging/local OAuth setups, with current-user keychain support, env-injected tokens, and stronger redaction.

  • New Features

    • Read creds from CLAUDE_CONFIG_DIR; pass expanded home path to ccusage.
    • Support staging/local/custom OAuth endpoints and client IDs; keychain service names include an OAuth suffix.
    • Accept CLAUDE_CODE_OAUTH_TOKEN for inference-only sessions; skip live usage when missing user:profile.
    • Include user:file_upload in OAuth refresh scope.
  • Bug Fixes

    • macOS keychain prefers current-user entries with legacy fallback and writes back to the same source. Added readGenericPasswordForCurrentUser/writeGenericPasswordForCurrentUser.
    • Expanded host API env allowlist for Claude auth vars (CLAUDE_CONFIG_DIR, CLAUDE_CODE_OAUTH_TOKEN, USER_TYPE, USE_STAGING_OAUTH, USE_LOCAL_OAUTH, CLAUDE_CODE_CUSTOM_OAUTH_URL, CLAUDE_CODE_OAUTH_CLIENT_ID, CLAUDE_LOCAL_OAUTH_API_BASE).
    • Redaction covers team/payment IDs, account names, and filesystem paths; app_data_dir and ccusage paths masked as [PATH].
    • Expanded tilde path expansion for ccusage home and improved 401 retry handling. Added tests for keychain current-user/fallback, service suffixes, custom home, env tokens, scopes, usage-skip, and redaction.

Written for commit 110bee6. Summary will update on new commits.

@github-actions github-actions bot added rust Pull requests that update rust code plugin labels Mar 31, 2026
@augmentcode
Copy link
Copy Markdown

augmentcode bot commented Mar 31, 2026

🤖 Augment PR Summary

Summary: This PR improves Claude plugin authentication compatibility across custom config locations and multiple OAuth environments, and strengthens host-side redaction/logging around those auth flows.

Changes:

  • Claude plugin now reads credentials from CLAUDE_CONFIG_DIR, supports staging/local/custom OAuth bases, and supports env-injected access tokens.
  • Keychain credential lookup/refresh now uses an OAuth-environment-specific service name and refresh scope includes user:file_upload.
  • Claude usage querying via ccusage can receive an explicit home path override.
  • Host API expands the env allowlist for Claude auth inputs and adds more robust redaction of IDs and filesystem paths.
  • macOS keychain integration now targets an explicit account (current user) and adds structured logging for keychain hits/misses/writes.
  • Adds JS plugin regression tests for the new Claude auth behaviors and Rust tests for env allowlisting, keychain args, and redaction.

Technical Notes: Live usage fetch is skipped for “inference-only” env-injected tokens, but ccusage-based historical usage is still queried.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

accessToken = refreshed
} else {
ctx.host.log.warn("proactive refresh failed, trying with existing token")
const homePath = getClaudeHomeOverride(ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

homePath is taken directly from CLAUDE_CONFIG_DIR and passed through to ccusage, but the Rust ccusage wrapper sets the env var without ~ expansion (unlike host.fs.* paths). If a user sets CLAUDE_CONFIG_DIR=~/.claude-custom, credential reads may work while ccusage queries silently point at a non-existent directory.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

let old = std::env::var(name).ok();
let _restore = RestoreEnvVar { name, old };
// SAFETY: this test restores the previous value in `Drop`.
unsafe { std::env::set_var(name, "openusage-test-user") };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates the process-wide USER env var and assumes env changes are “serialized”, but Rust tests run in parallel by default so this can be flaky and/or leak into other tests. If any other test reads USER concurrently, results can become nondeterministic.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@robinebers robinebers linked an issue Mar 31, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 5 files

@validatedev validatedev requested a review from Copilot April 1, 2026 11:50
@validatedev
Copy link
Copy Markdown
Collaborator

@codex review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves Claude plugin authentication compatibility across custom config directories and multiple OAuth environments, while expanding redaction coverage and adding regression tests.

Changes:

  • Added support for CLAUDE_CONFIG_DIR, env-injected OAuth tokens, and staging/local/custom OAuth routing in the Claude plugin.
  • Updated Rust host API to broaden env allowlisting, improve macOS keychain access/logging, and redact additional IDs/paths in logs/bodies.
  • Added JS + Rust tests for the new Claude auth flows, ccusage home overrides, and redaction behavior.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src-tauri/src/plugin_engine/host_api.rs Expands env allowlist, adds keychain account scoping + logging, ccusage home expansion, and broader redaction.
src-tauri/src/lib.rs Redacts app_data_dir logging output while preserving a non-sensitive tail.
plugins/claude/plugin.js Adds CLAUDE_CONFIG_DIR support, OAuth env routing, env token handling, scope expansion, and usage-fetch gating.
plugins/claude/plugin.test.js Adds regression tests for new credential discovery, keychain service suffixes, env token behavior, and refresh scope.
.gitignore Ignores a new docs directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1841 to 1852
let account = current_macos_keychain_account();
let args = keychain_find_generic_password_args(&service, &account);
let redacted_account = redact_value(&account);
log::info!(
"[plugin:{}] keychain read: service={}, account={}",
pid_read,
service,
redacted_account
);
let output = std::process::Command::new("security")
.args(["find-generic-password", "-s", &service, "-w"])
.args(&args)
.output()
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readGenericPassword now always queries the keychain with both -s <service> and -a <current account>. Any existing items that were previously created without -a (or with a different account value) will not be found anymore, which can silently break credential discovery after upgrade. Consider falling back to a service-only lookup when the account-scoped lookup misses (and/or migrating the item to the account-scoped form once found) so existing installs continue to work.

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +340
if let Ok(path_re) =
regex_lite::Regex::new(r#"(/(?:Users|home|opt|private|var|tmp|Applications)/[^\s"')]+)"#)
{
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path redaction regex only matches Unix-style absolute paths starting with /Users, /home, /opt, etc. On Windows, common sensitive paths like C:\Users\... or UNC paths (\\server\share\...) will not be redacted and may still leak via logs. Consider adding Windows/UNC path patterns (and tests) so redaction is effective cross-platform.

Suggested change
if let Ok(path_re) =
regex_lite::Regex::new(r#"(/(?:Users|home|opt|private|var|tmp|Applications)/[^\s"')]+)"#)
{
if let Ok(path_re) = regex_lite::Regex::new(
r#"((?:/(?:Users|home|opt|private|var|tmp|Applications)|[A-Za-z]:\\|\\\\)[^\s"')]+)"#,
) {

Copilot uses AI. Check for mistakes.
Comment on lines +269 to +272
if (Array.isArray(scopes) && scopes.length > 0) {
return scopes.indexOf("user:profile") !== -1
}
return true
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasProfileScope returns true when creds.oauth.scopes is an empty array, which effectively treats an explicitly-specified “no scopes” token as having user:profile and will attempt the live usage call. If the intent is to skip live usage whenever user:profile is missing, this should return scopes.includes("user:profile") for any array (including empty).

Suggested change
if (Array.isArray(scopes) && scopes.length > 0) {
return scopes.indexOf("user:profile") !== -1
}
return true
if (!Array.isArray(scopes)) {
return false
}
return scopes.includes("user:profile")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc1d1806e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +73 to +75
OsString::from("-a"),
OsString::from(account),
OsString::from("-s"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add fallback keychain lookup when account-scoped read misses

This change makes keychain reads require an exact account match (-a <current user>), but existing items created before this change may not match that account, so credential discovery can regress to "not logged in" after upgrade. In this codepath, a miss is treated as absent credentials rather than retried with a service-only lookup, so affected users lose persisted auth until they re-login or manually migrate keychain items. Add a fallback find-generic-password -s <service> -w (or migration) when account-scoped lookup fails.

Useful? React with 👍 / 👎.

Comment on lines +338 to +340
if let Ok(path_re) =
regex_lite::Regex::new(r#"(/(?:Users|home|opt|private|var|tmp|Applications)/[^\s"')]+)"#)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact full filesystem paths that contain spaces

The new path redaction regex stops at whitespace, so common macOS paths like /Users/.../Library/Application Support/... are only partially masked (e.g., leaving Support/... in logs). Since this commit now logs redacted app_data_dir on startup, the partial match still leaks path fragments in routine logs. Update the pattern to consume full quoted/unquoted path segments including spaces.

Useful? React with 👍 / 👎.

@robinebers robinebers force-pushed the fix/claude-plugin-auth-compat branch from d8a3187 to 110bee6 Compare April 7, 2026 06:31
@robinebers
Copy link
Copy Markdown
Owner Author

for the record, I've added two new host APIs that read keychain items for the specific user. We should seek to deprecate the entire host if possible in the future, but more research for every plugin is required.

@robinebers robinebers merged commit 9a7a640 into main Apr 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claud useage is not visable

3 participants