T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/poll_daemon.py:27
- Finding
- API Key Disclosure Through Unrestricted Polling Base URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/poll_daemon.py`, lines 27–76 **Vulnerability Type**: Bearer credential exposure through a user-controlled network destination **Risk Level**: High ### Vulnerable Code ```python parser.add_argument("--base-url", default="https://api.thrd.email") parser.add_argument("--cursor-file", default=".thrd_cursor") parser.add_argument("--timeout-ms", type=int, default=25000) parser.add_argument("--limit", type=int, default=50) args = parser.parse_args() api_key = os.environ.get("THRD_API_KEY") if not api_key: print(json.dumps({"ok": False, "error": "THRD_API_KEY environment variable not set."})) return 1 base_url = args.base_url.rstrip("/") cursor_path = Path(args.cursor_file) cursor = load_cursor(cursor_path) headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } print(f"Thrd poll daemon started (cursor={cursor}, file={cursor_path}).", file=sys.stderr) while True: try: resp = requests.get( f"{base_url}/v1/events", headers=headers, params={"cursor": cursor, "timeout": args.timeout_ms, "limit": args.limit}, timeout=(args.timeout_ms / 1000.0) + 10, ) resp.raise_for_status() data = resp.json() events = data.get("events", []) next_cursor = data.get("next_cursor", cursor) if events: summary = { "ok": True, "received": len(events), "cursor": next_cursor, "types": [ev.get("type") for ev in events], } print(json.dumps(summary)) ack = requests.post( f"{base_url}/v1/events/ack", headers=headers, json={"cursor": str(next_cursor)}, timeout=30, ) ``` ### Technical Analysis The `--base-url` command-line argument controls the origin of both polling and acknowledgment requests. ...[truncated 2004 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `--base-url` from production use and hardcode the authenticated endpoint: ```python BASE_URL = "https://api.thrd.email" ``` 2. If endpoint configurability is required for testing, validate the parsed URL before constructing authentication headers: ```python from urllib.parse import urlparse parsed = urlparse(args.base_url) if ( parsed.scheme != "https" or parsed.hostname != "api.thrd.email" or parsed.port not in (None, 443) or parsed.username is not None or parsed.password is not None ): raise ValueError("Unapproved THRD API origin") ``` 3. Use separate test credentials for development endpoints rather than transmitting production credentials to configurable origins. 4. Construct or attach the Authorization header only after destination validation succeeds. 5. Consider using a configured `requests.Session` with a policy that prevents credentials from being attached to unapproved origins. 6. Add automated tests confirming rejection of HTTP, alternate domains, subdomain tricks, nonstandard ports, embedded credentials, and malformed URLs. 7. Rotate any API key that may already have been used with an untrusted `--base-url`. ]]>
