T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/ryot_api.py:21
- Finding
- Bearer Token May Be Transmitted to an Untrusted or Unencrypted Endpoint<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/ryot_api.py:21-43` - `scripts/ryot-mark-episodes.py:19-42` - `scripts/ryot_calendar.py:18-31` - `scripts/ryot_collections.py:15-30` - `scripts/ryot_reviews.py:15-30` - `scripts/ryot_stats.py:15-30` **Vulnerability Type**: Unvalidated destination for authenticated network requests **Risk Level**: High ### Vulnerable Code The following representative implementation appears in `scripts/ryot_api.py`: ```python def graphql_request(query, variables=None): """Execute a GraphQL request to Ryot API.""" config = load_config() url = f"{config['url']}/backend/graphql" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {config['api_token']}", "User-Agent": "Ryot-API-Client/1.0" } data = {"query": query} if variables: data["variables"] = variables req = urllib.request.Request( url, data=json.dumps(data).encode(), headers=headers, method="POST" ) with urllib.request.urlopen(req) as response: return json.loads(response.read().decode()) ``` The other listed scripts use the same security-sensitive pattern: the configured URL is concatenated with `/backend/graphql`, and the API token is placed in the `Authorization` header without validating the URL scheme or destination. ### Technical Analysis The scripts trust the `url` value loaded from `/home/node/clawd/config/ryot.json`. They do not parse the URL or enforce HTTPS before attaching the reusable bearer token. If the configuration contains an `http://` URL, the authorization header and private GraphQL data can be transmitted without transport encryption. A network attacker capable of observing or modifying that connection may recover the token. If the configuration is modified to reference an attacker-controlled server, invoking any affected command causes the token to be sent directly to that server. This is especially ...[truncated 1796 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate the configured URL before creating the request: ```python from urllib.parse import urlsplit def validate_base_url(value): parsed = urlsplit(value) if parsed.scheme != "https": raise ValueError("The Ryot URL must use HTTPS") if not parsed.hostname: raise ValueError("The Ryot URL must contain a valid hostname") if parsed.username or parsed.password: raise ValueError("Embedded URL credentials are not allowed") if parsed.query or parsed.fragment: raise ValueError("Query strings and fragments are not allowed") return value.rstrip("/") ``` 2. Use the validated value consistently in every script: ```python base_url = validate_base_url(config["url"]) url = f"{base_url}/backend/graphql" ``` 3. If local development requires HTTP, allow it only through an explicit opt-in setting and restrict it to approved loopback addresses such as `127.0.0.1` or `::1`. 4. Consider an optional hostname allowlist or display the resolved destination before first use. 5. Add a finite network timeout: ```python with urllib.request.urlopen(req, timeout=30) as response: ... ``` 6. Avoid forwarding authorization headers across redirects. Prefer rejecting redirects or verifying that the redirect destination has the same HTTPS origin before resending credentials. 7. Centralize configuration and request handling in one reviewed module so all scripts receive identical validation and transport protections. ]]>
