T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/ecommerce_ad_copy_generator.py:85
- Finding
- Fail-Open Billing Verification Permits Unpaid Content Generation## Vulnerability Details **File Location**: `scripts/ecommerce_ad_copy_generator.py`, lines 85-92 and 145-158 **Vulnerability Type**: Fail-open validation of billing API responses **Risk Level**: High ### Vulnerable Code ```python def _safe_json_load(raw: bytes) -> dict[str, Any]: if not raw: return {} try: return json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): return {} ``` ```python with request.urlopen(req, timeout=self.timeout_seconds) as response: response_data = _safe_json_load(response.read()) status = getattr(response, "status", 200) success = bool(response_data.get("success", True)) and status < 300 return ChargeResult( success=success, transaction_id=str(response_data.get("transaction_id", "")) or None, payment_url=_normalize_payment_url(response_data, user_id), status_code=status, error_code=None if success else _extract_error_code(response_data), raw_response=response_data, ) ``` ### Technical Analysis The billing response parser converts an empty response body, invalid UTF-8, or malformed JSON into an empty dictionary. The success check then reads the `success` property with a default value of `True`: ```python response_data.get("success", True) ``` Consequently, any HTTP response with a status below 300 is considered a successful charge when the response omits `success` or cannot be parsed. The use of `bool(...)` also accepts incorrectly typed truthy values. For example, the string `"false"` evaluates to `True` in Python. No valid transaction identifier is required before paid content is generated. This violates the fail-closed behavior expected for a billing authorization boundary. ### Attack Path 1. An attacker influences the configured billing endpoint, an intermediary proxy, or a mock billing service, or takes advantage of a billing service returning an unexpected response. 2. The endpoint respond ...[truncated 1103 chars]
- Remediation
- ## Remediation Suggestions 1. Make malformed, empty, or non-object JSON responses explicit billing failures rather than returning an empty dictionary. 2. Require an exact Boolean success value: ```python if response_data.get("success") is not True: return ChargeResult( success=False, status_code=status, error_code=_extract_error_code(response_data), raw_response=response_data, ) ``` 3. Validate the complete response schema, including field types and required fields. 4. Require a non-empty, correctly typed `transaction_id` before allowing content generation. 5. Reject unexpected response content types and oversized response bodies. 6. Treat all ambiguous conditions as billing failures and do not generate paid output unless the charge is positively verified. 7. Add tests covering: - Empty HTTP 200 responses - Malformed JSON in HTTP 200 responses - `{}` in HTTP 200 responses - Missing `success` - `"success": "false"` - `"success": 1` - Successful responses without a transaction ID 8. Enforce HTTPS for `SKILLPAY_CHARGE_ENDPOINT` and preferably allowlist the expected billing host, especially when attaching `SKILLPAY_API_KEY`.
