Back to skill

Security audit

OpenAI Codex Multi OAuth

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate Codex OAuth debugging purpose, but its default diagnostics can expose unredacted account, session, and usage details without clear opt-in.

Use this skill only when you are comfortable letting the agent inspect local OpenClaw OAuth profile and session files. Run diagnostics locally, prefer selecting specific profiles, review or redact output before sharing it, and treat JSON output from the usage script as potentially containing raw account metadata.

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

Warning
Location
scripts/codex_usage_report.py:167
Finding
Raw usage API response is disclosed in JSON output without explicit opt-in<![CDATA[ ## Vulnerability Details **File Location**: `scripts/codex_usage_report.py`, lines 167-179, 230, 249-251, and 260 **Vulnerability Type**: Sensitive data exposure caused by ineffective output gating **Risk Level**: Medium ### Vulnerable Code ```python def summarize_usage(data: dict[str, Any]): if data.get('error'): return {'error': data['error']} usage_windows = [] for key in ('primary_window', 'secondary_window'): line = window_line(((data.get('rate_limit') or {}).get(key) or {})) if line: usage_windows.append(line) review_windows = [] for key in ('primary_window', 'secondary_window'): line = window_line(((data.get('code_review_rate_limit') or {}).get(key) or {})) if line: review_windows.append(line) return { 'user_id': data.get('user_id'), 'account_id': data.get('account_id'), 'email': data.get('email'), 'plan_type': data.get('plan_type'), 'usage_windows': usage_windows, 'code_review_windows': review_windows, 'raw': data, } ``` The CLI presents raw output as optional: ```python parser.add_argument( '--raw', action='store_true', help='Include raw API payloads in JSON output' ) ``` However, `usage` already contains the raw response before this condition is evaluated: ```python usage = summarize_usage(raw) row = { 'profileId': item['profileId'], 'email': item.get('email'), 'accountId': item.get('accountId'), 'workspace': item.get('workspace'), 'isActiveProfile': item['profileId'] == active_profile_id, 'usage': usage, } if args.raw and not usage.get('error'): row['raw'] = usage.get('raw') ``` The entire structure is then serialized: ```python if args.json: print(json.dumps(payload, ensure_ascii=False, indent=2)) ``` ### Technical Analysis The `--raw` option is intended to control whether the complete response from the authenticated `wham/usage` endpoint app ...[truncated 2356 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place the raw response inside the summarized object: ```python def summarize_usage(data: dict[str, Any]): if data.get('error'): return {'error': data['error']} # Build windows as before. return { 'user_id': data.get('user_id'), 'account_id': data.get('account_id'), 'email': data.get('email'), 'plan_type': data.get('plan_type'), 'usage_windows': usage_windows, 'code_review_windows': review_windows, } ``` 2. Retain the original response separately and add it only when the operator explicitly supplies `--raw`: ```python raw = fetch_usage(item, timeout_seconds=args.timeout) usage = summarize_usage(raw) row = { 'profileId': item['profileId'], 'email': item.get('email'), 'accountId': item.get('accountId'), 'workspace': item.get('workspace'), 'isActiveProfile': item['profileId'] == active_profile_id, 'usage': usage, } if args.raw and not usage.get('error'): row['raw'] = raw ``` 3. Add regression tests verifying that: - `--json` never contains a `raw` key by default. - `--json --raw` contains the raw response exactly once. - Newly introduced API fields do not enter summarized output automatically. 4. Display a warning when `--raw` is used, explaining that the output may contain account identifiers and should not be shared without review. 5. Consider redacting email and stable account identifiers in default JSON output, with a separate opt-in option for full identity details. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/summarize_codex_profiles.py:122
Finding
Default diagnostic report exposes unredacted profile and session-routing identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/summarize_codex_profiles.py`, lines 122-146 and 208-244; default invocation is recommended in `SKILL.md`, lines 27-31 **Vulnerability Type**: Excessive disclosure of local identity and messaging metadata **Risk Level**: Low ### Vulnerable Code Recent session entries include session keys and messaging destinations: ```python def summarize_recent_sessions(sessions: dict[str, Any], limit: int, agent: str): items = [] prefix = f'agent:{agent}:' for key, entry in sessions.items(): if not isinstance(entry, dict): continue if not str(key).startswith(prefix): continue if ':subagent:' in str(key) or ':cron:' in str(key): continue items.append({ 'key': key, 'updatedAt': int(entry.get('updatedAt') or 0), 'channel': (entry.get('deliveryContext') or {}).get('channel') or entry.get('lastChannel') or (entry.get('origin') or {}).get('provider'), 'chatType': entry.get('chatType') or (entry.get('origin') or {}).get('chatType'), 'target': (entry.get('deliveryContext') or {}).get('to') or entry.get('lastTo') or (entry.get('origin') or {}).get('to'), 'modelProvider': entry.get('modelProvider') or entry.get('providerOverride'), 'model': entry.get('model'), 'authProfileOverride': entry.get('authProfileOverride'), 'authProfileOverrideSource': entry.get('authProfileOverrideSource'), }) ``` Profile identity fields are printed without redaction: ```python for item in summary['profiles']: print( ' - ' f"{item['profileId']} | " f"accountId={item['accountId'] or '-'} | " f"email={item['email'] or '-'} | " f"workspace={item['workspace'] or '-'} | " f"type={item['type'] or '-'} | " f"token={item['tokenState'] or '-'} | " f"lastGood={item['lastGood'] or '-'}" ) `` ...[truncated 3506 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Redact stable identifiers by default. For example: - Convert `user@example.com` to `u***@example.com`. - Show only a short suffix of account and workspace IDs. - Hash or truncate session keys and messaging targets. 2. Add explicit disclosure flags, such as: ```text --show-identifiers --include-sessions --show-targets ``` Default output should omit session destinations and full profile identity fields. 3. Separate diagnostic modes: - A minimal mode showing profile IDs, auth order, active profile, and override consistency. - An identity mode for email/account comparison. - A session-routing mode for resolving a specific reported chat. 4. When `--session-key` is supplied, report only that session unless the operator separately requests recent sessions. 5. Add a warning before full-identifier output: ```text Warning: this report contains account and messaging identifiers. Review and redact it before sharing. ``` 6. Ensure JSON output follows the same redaction policy as human-readable output. 7. Update `SKILL.md` to describe the sensitivity of diagnostic output and recommend redacted output for general troubleshooting. 8. Add tests confirming that the default output does not contain full emails, full session keys, or complete messaging targets. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill instructs the agent to read local files and run local scripts, but it does not declare any explicit tool scope such as allowed tools or permissions. This creates a capability/scope mismatch: a host may grant broader file or network access than an operator expects, increasing the risk of unintended data exposure or over-privileged execution during troubleshooting.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The guidance explicitly recommends displaying `user_id`, `account_id`, and `email` during debugging, which are sensitive identifiers that can expose personal or account-linkage data in logs, screenshots, chat transcripts, or support channels. In this skill context, debugging multi-profile OAuth and usage mismatches increases the chance that these identifiers are surfaced across multiple accounts or workspaces, making accidental disclosure more likely.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code loads auth profile data from local state files, extracts access tokens, and uses them in an HTTP request to the ChatGPT usage endpoint. While the script purpose is to fetch usage, there is no user-facing warning in comments or CLI help that it accesses sensitive credential material and transmits it over the network.

External Transmission

Medium
Category
Data Exfiltration
Content
payload = access.split('.')[1]
        payload += '=' * (-len(payload) % 4)
        decoded = json.loads(base64.urlsafe_b64decode(payload.encode()).decode())
        return ((decoded.get('https://api.openai.com/profile') or {}).get('email'))
    except Exception:
        return None
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
payload = access.split('.')[1]
        payload += '=' * (-len(payload) % 4)
        decoded = json.loads(base64.urlsafe_b64decode(payload.encode()).decode())
        return ((decoded.get('https://api.openai.com/profile') or {}).get('email'))
    except Exception:
        return None
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script prints profile emails, session targets, and profile identifiers directly to stdout in both human-readable and JSON modes. Because this skill is specifically for inspecting OAuth profile state and debugging account/workspace selection, the output is likely to contain sensitive identity and routing metadata that can be exposed through terminal logs, screenshots, shell history capture, CI logs, or pasted diagnostics.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The script prints stored and API-returned email addresses, user IDs, and account IDs to stdout. The current help text does not warn users that personally identifying account data will be displayed, which can matter in shared terminals or logs.

Static analysis

No suspicious patterns detected.