-
Notifications
You must be signed in to change notification settings - Fork 245
fix(claude): handle hex-encoded keychain credentials #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Added a new function to parse credentials from hex-encoded JSON format, improving compatibility with macOS keychain items. - Updated the credential loading logic to utilize the new parsing function for both file and keychain sources. - Added a test case to verify the correct handling of hex-encoded JSON credentials.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,12 +7,39 @@ | |
| const SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers" | ||
| const REFRESH_BUFFER_MS = 5 * 60 * 1000 // refresh 5 minutes before expiration | ||
|
|
||
| function tryParseCredentialJSON(text) { | ||
| if (!text) return null | ||
| const trimmed = String(text).trim() | ||
| if (!trimmed) return null | ||
| try { | ||
| return JSON.parse(trimmed) | ||
| } catch {} | ||
|
|
||
| // Some macOS keychain items are returned by `security ... -w` as hex-encoded UTF-8 bytes. | ||
| // Example prefix: "7b0a" ( "{\\n" ). | ||
| // Support both plain hex and "0x..." forms. | ||
| let hex = trimmed | ||
| if (hex.startsWith("0x") || hex.startsWith("0X")) hex = hex.slice(2) | ||
| if (!hex || hex.length % 2 !== 0) return null | ||
| if (!/^[0-9a-fA-F]+$/.test(hex)) return null | ||
| try { | ||
| let decoded = "" | ||
| for (let i = 0; i < hex.length; i += 2) { | ||
| decoded += String.fromCharCode(parseInt(hex.slice(i, i + 2), 16)) | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| return JSON.parse(decoded) | ||
| } catch {} | ||
|
|
||
| return null | ||
| } | ||
|
|
||
| function loadCredentials(ctx) { | ||
| // Try file first | ||
| if (ctx.host.fs.exists(CRED_FILE)) { | ||
| try { | ||
| const text = ctx.host.fs.readText(CRED_FILE) | ||
| const parsed = JSON.parse(text) | ||
| const parsed = tryParseCredentialJSON(text) | ||
| if (!parsed) return null | ||
|
cursor[bot] marked this conversation as resolved.
Outdated
|
||
| const oauth = parsed.claudeAiOauth | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the credentials file exists but contains malformed JSON (e.g., partial write), Useful? React with 👍 / 👎. |
||
| if (oauth && oauth.accessToken) { | ||
| return { oauth, source: "file", fullData: parsed } | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
@@ -25,7 +52,8 @@ | |
| try { | ||
| const keychainValue = ctx.host.keychain.readGenericPassword(KEYCHAIN_SERVICE) | ||
| if (keychainValue) { | ||
| const parsed = JSON.parse(keychainValue) | ||
| const parsed = tryParseCredentialJSON(keychainValue) | ||
| if (!parsed) return null | ||
| const oauth = parsed.claudeAiOauth | ||
| if (oauth && oauth.accessToken) { | ||
| return { oauth, source: "keychain", fullData: parsed } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.