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. ]]>
