T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/whoop_oauth_login.py:52
- Finding
- OAuth Authorization Flow Lacks State and Callback Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/whoop_oauth_login.py:52-72` **Vulnerability Type**: OAuth login CSRF and account-binding confusion **Risk Level**: High ### Vulnerable Code ```python params = { "client_id": client_id, "redirect_uri": redirect_uri, "response_type": "code", "scope": scopes, } auth_url = OAUTH_AUTH_URL + "?" + urllib.parse.urlencode(params) print("Open this URL in a browser and approve access:\n") print(auth_url) print("\nAfter approval, paste either the full redirect URL or just the code:") user_in = input("> ") code = parse_code(user_in) tok = exchange_code_for_token( code=code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, ) ``` The associated parser at `scripts/whoop_oauth_login.py:34-44` only extracts the authorization code: ```python def parse_code(user_input: str) -> str: s = user_input.strip() if s.startswith("http://") or s.startswith("https://"): u = urllib.parse.urlparse(s) q = urllib.parse.parse_qs(u.query) code = (q.get("code") or [None])[0] if code: return code raise SystemExit("Could not find ?code= in the pasted redirect URL") return s ``` ### Technical Analysis The authorization request does not include a cryptographically random OAuth `state` parameter. The callback parser accepts a redirect URL from any HTTP or HTTPS origin, extracts only its `code`, and does not verify that its scheme, host, port, and path match the configured `WHOOP_REDIRECT_URI`. The authorization code is therefore not bound to the login transaction initiated by this process. Accepting a bare code further prevents callback-origin and state validation. This creates login CSRF or account-binding confusion risk: a valid authorization code generated through a different browser transaction can be submitted and stored by the Skill. PKCE is also absent. Although the client secret is required for exchang ...[truncated 1237 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Generate a fresh state value for every authorization attempt: ```python import secrets expected_state = secrets.token_urlsafe(32) params["state"] = expected_state ``` 2. Require the user to provide the complete callback URL rather than permitting a bare authorization code. 3. Parse the callback and compare `state` using `secrets.compare_digest`. 4. Compare the callback scheme, hostname, effective port, and path against `WHOOP_REDIRECT_URI` before accepting the code. 5. Reject callbacks containing OAuth `error` fields, missing state, duplicate state values, or multiple authorization codes. 6. Keep transaction state only for the short duration of the login flow and invalidate it after one use. 7. Add PKCE with a random verifier and `S256` challenge if supported by WHOOP. 8. Prefer a temporary localhost callback listener that validates the callback automatically instead of manual copy-and-paste. ]]>
