Back to skill

Security audit

Codex Switcher

Security checks for vulnerabilities and agentic risk

Overview

This skill appears purpose-built for Codex account switching, but it handles long-lived OAuth tokens and has an under-scoped endpoint override that could leak credentials in a tampered environment.

Review carefully before installing. Use only in a trusted local environment, unset CS_OAUTH_AUTHORIZE_URL and CS_OAUTH_TOKEN_URL unless you are deliberately testing, protect ~/.openclaw so only your user can read it, and treat all snapshot and backup files as account credentials.

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/cs.sh:9
Finding

Environment-Controlled OAuth Endpoint Can Exfiltrate Authentication Credentials

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cs.sh:80
Finding

Pending OAuth State File Is Created Without Explicit Restrictive Permissions

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 76)May include surrounding context.

md
- temporary pending OAuth state files under `~/.openclaw/`

Treat all snapshot files as secrets.
Never expose full access tokens or refresh tokens in chat.

## Safety rules

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

This flow is centered on handling refresh tokens to mint new access tokens, which is inherently sensitive because possession of the refresh token can enable long-lived account access. Although the README says not to print full tokens, it still prescribes persistent local storage and token reuse without specifying protections such as secure storage, scope minimization, revocation handling, or file-permission controls.

Content

Scanner excerpt · scripts/README-local-flow.md (reported line 35)May include surrounding context.

md
### Refresh snapshots
1. Read snapshot `expires`
2. Skip snapshots not close to expiry
3. Use snapshot `refresh` token to get a new access token
4. Write refreshed credentials back into the same snapshot file

## Never do

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · SKILL.md (reported line 80)May include surrounding context.

md
## Never do

- `curl | bash`
- hidden proxy fallback
- writing unrelated config
- bloating `openclaw.json` with a growing Codex roster

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · scripts/README-local-flow.md (reported line 40)May include surrounding context.

md
## Never do

- `curl | bash`
- hidden proxy fallback
- writing unrelated config
- bloating `openclaw.json` with a growing Codex roster

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill describes capabilities to read/write high-sensitivity auth files and perform networked OAuth/quota operations, but it declares no explicit tool scope or permissions boundary. In a system that relies on manifest-declared scopes, this creates an under-specified trust boundary and increases the chance the skill is invoked with broader file and network access than reviewers or users expect.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
89% confidence
Finding

The skill is explicitly designed to persist OAuth snapshots and refresh them over time, which creates long-lived local session material including refresh tokens. Even though this is core functionality, persistent credential storage materially raises compromise impact: theft of snapshot files can enable account takeover or silent token renewal across multiple Codex accounts.

Content

Scanner excerpt · SKILL.md (reported line 56)May include surrounding context.

md
2. sign in in browser
3. run `cs add --apply '<callback-url>' [alias]`
4. if alias was omitted, derive it from the email automatically
5. create a new snapshot file for that account

### `cs refresh <alias>`
Force-refresh one snapshot using its refresh token.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The document explicitly instructs implementers to store OAuth access and refresh tokens in local snapshot files and to modify the active auth profile, but it does not require user consent, secure file permissions, encryption, or a warning that these files are highly sensitive. In a credential-management skill, this omission increases the chance of unsafe implementations that expose reusable authentication material or silently switch active identities.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cs.sh (reported line 155)May include surrounding context.

sh
payload_b64 = access.split('.')[1]
payload_b64 += '=' * (-len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64.encode()).decode())
auth_claim = payload.get('https://api.openai.com/auth', {}) if isinstance(payload, dict) else {}
profile_claim = payload.get('https://api.openai.com/profile', {}) if isinstance(payload, dict) else {}
account_id = str(auth_claim.get('chatgpt_account_id', '')).strip()
email = str(profile_claim.get('email', '')).strip()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cs.sh (reported line 156)May include surrounding context.

sh
payload_b64 = access.split('.')[1]
payload_b64 += '=' * (-len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64.encode()).decode())
auth_claim = payload.get('https://api.openai.com/auth', {}) if isinstance(payload, dict) else {}
profile_claim = payload.get('https://api.openai.com/profile', {}) if isinstance(payload, dict) else {}
account_id = str(auth_claim.get('chatgpt_account_id', '')).strip()
email = str(profile_claim.get('email', '')).strip()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cs.sh (reported line 229)May include surrounding context.

sh
payload_b64 = access.split('.')[1]
payload_b64 += '=' * (-len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64.encode()).decode())
auth_claim = payload.get('https://api.openai.com/auth', {}) if isinstance(payload, dict) else {}
profile_claim = payload.get('https://api.openai.com/profile', {}) if isinstance(payload, dict) else {}
account_id = str(auth_claim.get('chatgpt_account_id', '')).strip()
email = str(profile_claim.get('email', '')).strip()

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cs.sh (reported line 249)May include surrounding context.

sh
payload_b64 = access.split('.')[1]
payload_b64 += '=' * (-len(payload_b64) % 4)
payload = json.loads(base64.urlsafe_b64decode(payload_b64.encode()).decode())
auth_claim = payload.get('https://api.openai.com/auth', {}) if isinstance(payload, dict) else {}
profile_claim = payload.get('https://api.openai.com/profile', {}) if isinstance(payload, dict) else {}
account_id = str(auth_claim.get('chatgpt_account_id', '')).strip()
email = str(profile_claim.get('email', '')).strip()

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

While the matched line itself is just local JWT parsing, the surrounding quota feature immediately uses the extracted active token to make an authenticated request to https://chatgpt.com/backend-api/wham/usage. This sends a bearer token and account identifier to an external service, which is expected for functionality but still security-relevant because a local skill handling multiple account snapshots can expose or misuse tokens if run in an untrusted context.

Content

Scanner excerpt · scripts/cs.sh (reported line 269)May include surrounding context.

sh
payload = access.split('.')[1]
        payload += '=' * (-len(payload) % 4)
        obj = json.loads(base64.urlsafe_b64decode(payload.encode()).decode())
        email = ((obj.get('https://api.openai.com/profile') or {}).get('email') or '-')
    headers = {
        'Authorization': f'Bearer {access}',
        'ChatGPT-Account-Id': prof.get('accountId', '')