T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/odds_api.py:112
- Finding
- API Key Disclosure Through Dry-Run Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/odds_api.py:112-127` **Additional Locations**: `scripts/odds_api.py:153-165`, `scripts/odds_api.py:173-184`, `scripts/odds_api.py:207-222`; `SKILL.md:32` **Vulnerability Type**: Sensitive credential exposure through standard output **Risk Level**: Medium ### Vulnerable Code ```python def command_events(args): api_key = get_api_key(args, required=True) params = { "apiKey": api_key, "sport": args.sport, "league": args.league, "participantId": args.participant_id, "status": args.status, "from": args.from_time, "to": args.to_time, "bookmaker": args.bookmaker, } url = build_url(args.base_url, "/events", params) if args.dry_run: print(url) return 0 ``` The same pattern appears in the authenticated `search`, `odds`, and `matchup` command paths. The corresponding documentation encourages this behavior: ```markdown Prefer `--dry-run` to preview the URL when testing without a key. ``` ### Technical Analysis Authenticated commands retrieve the API key from `--api-key` or the `ODDS_API_KEY` environment variable, insert it into the `apiKey` query parameter, and construct the complete request URL before processing `--dry-run`. When dry-run mode is enabled, the complete URL is printed without redacting the credential. Contrary to the documentation's statement that dry-run can be used when testing without a key, the authenticated commands call `get_api_key(args, required=True)` before checking `args.dry_run`. A real key is therefore still required and then disclosed. Because standard output is frequently captured by CI systems, Agent transcripts, shell session recording, test harnesses, and support tooling, printing the credential creates an avoidable disclosure risk. ### Attack Path 1. A user stores a valid Odds-API.io credential in `ODDS_API_KEY` or supplies it through `--api-key`. 2. The user, an aut ...[truncated 834 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Process dry-run mode without requiring a real API key. 2. Substitute a fixed placeholder such as `REDACTED` when generating preview URLs. 3. Introduce a centralized URL-redaction function and apply it before printing URLs or including them in errors. 4. Never write query-string credentials to stdout, stderr, logs, exceptions, or Agent responses. 5. Update `SKILL.md` to state explicitly that credentials are redacted and are not required for dry-run previews. 6. Add regression tests that assert neither environment-provided nor command-line API keys appear in captured output. Example hardening pattern: ```python def redact_url(url): parsed = urllib.parse.urlsplit(url) params = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True) safe_query = urllib.parse.urlencode( [(key, "REDACTED" if key == "apiKey" else value) for key, value in params] ) return urllib.parse.urlunsplit( (parsed.scheme, parsed.netloc, parsed.path, safe_query, parsed.fragment) ) if args.dry_run: preview_params = dict(params) preview_params["apiKey"] = "REDACTED" print(build_url(args.base_url, "/events", preview_params)) return 0 ``` ]]>
