T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/wait_for_task.py:35
- Finding
- API Token Exposed in URL Query Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wait_for_task.py`, lines 35–37; sensitive call sites at lines 103–106 and 128–131 **Vulnerability Type**: Credential exposure through URL query strings **Risk Level**: Medium ### Vulnerable Code ```python def request_json(endpoint, params, api_key, timeout): url = endpoint + "?" + urllib.parse.urlencode(params) request = urllib.request.Request(url, method="GET") try: with urllib.request.urlopen(request, timeout=timeout) as response: content = response.read() ``` The function is called with the API token included in `params`: ```python payload = request_json( STATUS_ENDPOINT, {"api_key": api_key, "task_id": task_id}, api_key, request_timeout, ) ``` The same pattern is used when downloading results: ```python return request_json( DOWNLOAD_ENDPOINT, {"api_key": api_key, "task_id": task_id, "type": "json"}, api_key, request_timeout, ) ``` ### Technical Analysis The Dataify API token is placed directly in the query string of status and download URLs. Although the endpoints use HTTPS, TLS only protects the request while it is in transit. Query strings can still be retained by HTTP server access logs, reverse proxies, gateways, observability platforms, debugging tools, and error reports. The response-body redaction performed later by the script does not protect the outbound request URL: ```python if api_key: text = text.replace(api_key, "<redacted>") ``` This only removes the token if Dataify reflects it in the response. It does not prevent the original URL from being logged. Transmitting authentication credentials in a URL exceeds the minimum exposure required for polling a task. The token should be sent through an authorization header instead. ### Attack Path 1. A user runs `wait_for_task.py` or a Builder workflow that invokes `wait_for_task`. 2. The script constructs requests such as: `https://scraperapi.dataify.com/ta ...[truncated 956 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from all URL query parameters. 2. Authenticate using the standard authorization header: ```python def request_json(endpoint, params, api_key, timeout): url = endpoint + "?" + urllib.parse.urlencode(params) request = urllib.request.Request( url, headers={"Authorization": "Bearer " + api_key}, method="GET", ) ``` 3. Update status and download calls so that only non-secret values are passed in `params`: ```python payload = request_json( STATUS_ENDPOINT, {"task_id": task_id}, api_key, request_timeout, ) ``` 4. If the remote API currently requires query-string authentication, update the service contract to support header-based credentials. Until that is possible: - Prevent request URLs from being logged. - Configure gateways and observability systems to redact `api_key`. - Use short-lived, narrowly scoped tokens where supported. 5. Add automated tests asserting that serialized request URLs never contain `DATAIFY_API_TOKEN` or an `api_key` parameter. 6. Rotate tokens that may already have appeared in request or infrastructure logs. ]]>
