Back to skill

Security audit

Claude OAuth Auto-Renewal

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent token-renewal purpose, but it automates sensitive Claude account authorization through Keychain and Chrome with weak consent boundaries and unsafe scripting details.

Install only if you are comfortable with an automated heartbeat process reading Claude Code Keychain credential data and driving Chrome to approve Claude OAuth. Prefer disabling Tier 2 browser automation unless strictly needed, use an isolated browser/profile if possible, avoid leaving Chrome Apple Events enabled broadly, and ask the publisher to add explicit runtime consent, safer temp-file handling, auth-code validation, and a non-dynamic Expect invocation before routine use.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/check-claude-oauth.sh:72
Finding
OAuth Callback Content Injection into Tcl/Expect Program<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check-claude-oauth.sh`, lines 72-91 and 124-150 **Vulnerability Type**: Improper neutralization of externally sourced data in dynamically generated Tcl/Expect code **Risk Level**: High ### Vulnerable Code ```bash extract_code_from_chrome() { osascript -l JavaScript -e ' function run() { const chrome = Application("Google Chrome"); const windows = chrome.windows(); for (const w of windows) { const tabs = w.tabs(); for (let i = 0; i < tabs.length; i++) { const url = tabs[i].url(); if (url.includes("platform.claude.com/oauth/code/callback")) { try { const code = tabs[i].execute({javascript: "(() => { const el = document.querySelector(\".font-mono, [class*=code], code, pre\"); return el ? el.textContent.trim() : \"no_element\"; })()" }); return code || "no_code"; } catch(e) { return "js_error:" + e.message; } } } } return "no_tab"; } ' 2>/dev/null || echo "osascript_error" } ``` ```bash auth_code="" for attempt in $(seq 1 10); do auth_code=$(extract_code_from_chrome) case "$auth_code" in no_tab|no_element|no_code|js_error:*|osascript_error) sleep 1 ;; *) break ;; esac done if [ -z "$auth_code" ] || [[ "$auth_code" == no_* ]] || [[ "$auth_code" == *error* ]]; then kill "$login_pid" 2>/dev/null || true return 1 fi # 5. Kill PTY process, feed code to fresh auth login via expect kill "$login_pid" 2>/dev/null || true sleep 1 expect -c " set timeout 30 spawn claude auth login expect { timeout { exit 1 } -re {visit:|browser} { sleep 6 } } send \"$auth_code\r\" expect { timeout { exit 1 } -re {successful|success|Login} { exit 0 } } " &>/tmp/claude-auth-expect.log ``` ### Technical Analysis The script extracts text from a browser page and int ...[truncated 2756 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never interpolate `auth_code` into dynamically generated Tcl source. 2. Pass the value through an environment variable or a positional argument and read it as data from a static Expect program. For example: ```bash export CLAUDE_AUTH_CODE="$auth_code" expect <<'EXPECT_EOF' set timeout 30 set auth_code $env(CLAUDE_AUTH_CODE) spawn claude auth login expect { timeout { exit 1 } -re {visit:|browser} { sleep 6 } } send -- "$auth_code\r" expect { timeout { exit 1 } -re {successful|success|Login} { exit 0 } } EXPECT_EOF unset CLAUDE_AUTH_CODE ``` 3. Use `send --` so values beginning with hyphens cannot be interpreted as options. 4. Validate the authorization code before passing it to Expect. Enforce the exact format documented by the authentication provider, including: - An allowlisted character set. - A reasonable minimum and maximum length. - Any required prefix or structural delimiters. 5. Replace the broad DOM selector with a precise selector tied to the expected callback-page element. 6. Reject values containing control characters, line breaks, Tcl metacharacters, or unexpected whitespace even when the value otherwise appears plausible. 7. Prefer a CLI-supported noninteractive OAuth flow or a documented callback mechanism over scraping browser-rendered content. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/check-claude-oauth.sh:96
Finding
Predictable Authentication Log Files in Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check-claude-oauth.sh`, lines 96-102 and 144-150 **Vulnerability Type**: Insecure temporary-file creation and retention of authentication-session output **Risk Level**: Medium ### Vulnerable Code ```bash auto_authorize() { local auth_code="" # 1. Start auth login with PTY (via script command) script -q /tmp/claude-auth-pty.log claude auth login &>/dev/null & local login_pid=$! ``` ```bash expect -c " set timeout 30 spawn claude auth login expect { timeout { exit 1 } -re {visit:|browser} { sleep 6 } } send \"$auth_code\r\" expect { timeout { exit 1 } -re {successful|success|Login} { exit 0 } } " &>/tmp/claude-auth-expect.log ``` ### Technical Analysis The authentication flow writes to two fixed paths in the globally shared `/tmp` directory: - `/tmp/claude-auth-pty.log` - `/tmp/claude-auth-expect.log` Neither path is securely created before use. The script does not use `mktemp`, verify file ownership, reject symbolic links, set a restrictive `umask`, or remove the files after completion. Predictable temporary paths create two related risks: 1. **Symbolic-link or file-clobber attacks:** A local attacker may pre-create one of the paths as a symbolic link. When the victim runs the heartbeat, `script` or the shell redirection may follow the link and overwrite a file writable by the victim. 2. **Authentication data retention:** PTY and Expect transcripts can contain login prompts, URLs, status output, and potentially echoed authorization material. Fixed log files remain after execution and may be readable by other local users depending on the process umask and file permissions. The exact disclosure or overwrite outcome depends on macOS temporary-directory behavior, ownership, permissions, the active umask, and whether the target utilities reject existing links. The implementation does not explicitly enforce the required ...[truncated 1598 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid recording authentication sessions unless the logs are strictly necessary. 2. Create a private temporary directory with restrictive permissions: ```bash umask 077 tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/claude-oauth.XXXXXX")" || exit 1 trap 'rm -rf -- "$tmp_dir"' EXIT HUP INT TERM pty_log="$tmp_dir/claude-auth-pty.log" expect_log="$tmp_dir/claude-auth-expect.log" ``` 3. If logs are unnecessary, redirect output to `/dev/null` instead of persistent files. 4. If logs are required for troubleshooting: - Create them inside the private temporary directory. - Ensure permissions are `0600`. - Redact authorization codes, callback URLs, tokens, and other sensitive values. - Delete them immediately after the operation. 5. Do not reuse predictable names in a shared directory. 6. Before writing any security-sensitive path, verify that it is a regular file owned by the current user and is not a symbolic link. 7. Consider keeping diagnostic logging disabled by default and enabling it only through an explicit configuration flag. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (12)

Credential Access

High
Category
Privilege Escalation
Content
Claude Code stores OAuth tokens in **macOS Keychain** under the service name `Claude Code-credentials`. The token JSON includes:

- `accessToken` — API access token (prefix `sk-ant-oat01-`)
- `refreshToken` — Used for automatic renewal (prefix `sk-ant-ort01-`)
- `expiresAt` — Unix timestamp in milliseconds
Confidence
80% confidence
Finding
The skill documentation explicitly describes the storage location, structure, and token prefixes for both access and refresh tokens. While not an exploit by itself, this materially lowers the barrier for credential harvesting by telling an operator or attacker exactly where high-value secrets reside and how to recognize them; in the context of an auto-renewal skill that handles OAuth tokens, that increases the danger.

Credential Access

High
Category
Privilege Escalation
Content
### "无法读取 Claude Code token"
- Run `claude auth login` manually to establish initial credentials
- Verify keychain access: `security find-generic-password -s "Claude Code-credentials" -a "$(whoami)" -g`

### Tier 2 (browser automation) not working
- Enable Chrome JXA: `View → Developer → Allow JavaScript from Apple Events`
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# For OpenClaw heartbeat integration
#
# Flow:
#   1. Read token expiry from macOS Keychain
#   2. Healthy (>WARN_HOURS) → silent exit
#   3. Expiring/expired → claude auth status (refresh token)
#   4. Refresh fails → claude auth login + Chrome auto-Authorize + extract code + feed to CLI
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# For OpenClaw heartbeat integration
#
# Flow:
#   1. Read token expiry from macOS Keychain
#   2. Healthy (>WARN_HOURS) → silent exit
#   3. Expiring/expired → claude auth status (refresh token)
#   4. Refresh fails → claude auth login + Chrome auto-Authorize + extract code + feed to CLI
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# For OpenClaw heartbeat integration
#
# Flow:
#   1. Read token expiry from macOS Keychain
#   2. Healthy (>WARN_HOURS) → silent exit
#   3. Expiring/expired → claude auth status (refresh token)
#   4. Refresh fails → claude auth login + Chrome auto-Authorize + extract code + feed to CLI
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
set -euo pipefail

WARN_HOURS=${WARN_HOURS:-6}
KEYCHAIN_SERVICE="Claude Code-credentials"
KEYCHAIN_ACCOUNT="$(whoami)"
AUTH_TIMEOUT=30
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
set -euo pipefail

WARN_HOURS=${WARN_HOURS:-6}
KEYCHAIN_SERVICE="Claude Code-credentials"
KEYCHAIN_ACCOUNT="$(whoami)"
AUTH_TIMEOUT=30
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
set -euo pipefail

WARN_HOURS=${WARN_HOURS:-6}
KEYCHAIN_SERVICE="Claude Code-credentials"
KEYCHAIN_ACCOUNT="$(whoami)"
AUTH_TIMEOUT=30
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README explicitly documents a workflow that reads stored OAuth credentials from the macOS Keychain, automates browser authorization, extracts an authorization code from a callback page, and feeds it back into the CLI. Even though this is presented as operational automation, it handles highly sensitive authentication material and account actions without any prominent warning, consent boundary, or discussion of security risks, which makes accidental credential exposure or unauthorized account action significantly more likely.

Session Persistence

Medium
Category
Rogue Agent
Content
### Tier 2 (browser automation) not working
- Enable Chrome JXA: `View → Developer → Allow JavaScript from Apple Events`
- Or via CLI: `defaults write com.google.Chrome AppleScriptEnabled -bool true` (restart Chrome)
- Ensure you're logged into claude.ai in Chrome

### JSON parsing errors
Confidence
92% confidence
Finding
The instruction to enable Chrome's `AppleScriptEnabled` / 'Allow JavaScript from Apple Events' weakens browser security by permitting automation of web content from local scripts. In this skill's context, that expanded automation capability is especially sensitive because it is used to drive OAuth login and authorization flows, creating a path for local script abuse, session hijacking, or unintended consent actions if the host or agent environment is compromised.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The script automates an OAuth authorization flow by controlling Chrome, clicking the Authorize button, extracting the authorization code from the callback page, and replaying it into `claude auth login` without explicit user consent at runtime. This bypasses the normal user-mediated trust boundary of OAuth and creates a powerful mechanism for silent account/session takeover if the script is triggered unexpectedly or modified to target other accounts or scopes.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The `get_expires_at` function accesses the macOS Keychain to retrieve Claude credential data, which is a sensitive credential store access. While this is commented in the file header, there is no visible prompt, log message, or user warning when the script performs the access.

Static analysis

No suspicious patterns detected.