T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/usda_fdc.py:22
- Finding
- API Credentials May Be Disclosed Through Unsanitized HTTP Exceptions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/usda_fdc.py:22-41`, `scripts/spoonacular.py:22-46`, `scripts/spoonacular.py:85-91`, `scripts/core.py:117-119`, and `scripts/core.py:349-350` **Vulnerability Type**: API credential exposure through exception messages **Risk Level**: Medium ### Vulnerable Code ```python # scripts/usda_fdc.py:22-31 def _handle_response(r: requests.Response): if r.status_code == 401: raise USDAError("USDA API key invalid or missing (401)", status=401) if r.status_code == 403: raise USDAError("Insufficient USDA API permissions (403)", status=403) if r.status_code == 429: raise USDAError("USDA API rate limit reached (429)", status=429) if r.status_code >= 500: raise USDAError(f"USDA API server error ({r.status_code})", status=r.status_code) r.raise_for_status() ``` ```python # scripts/usda_fdc.py:33-41 def search_food(query: str) -> List[Dict[str, Any]]: _require_key() url = f"{FDC_BASE}/foods/search" payload = { "query": query, "pageSize": SEARCH_PAGE_SIZE, "dataType": PREFERRED_DATA_TYPES, } r = requests.post( url, params={"api_key": USDA_API_KEY}, json=payload, timeout=HTTP_TIMEOUT_SEC, ) ``` ```python # scripts/spoonacular.py:35-46 def search_ingredient(name: str) -> List[Dict[str, Any]]: _require_key() url = f"{SPOONACULAR_BASE}/food/ingredients/search" params = { "query": name, "number": SPOONACULAR_SEARCH_LIMIT, "apiKey": SPOONACULAR_API_KEY, } r = requests.get(url, params=params, timeout=HTTP_TIMEOUT_SEC) ``` ```python # scripts/core.py:117-119 except SpoonacularError as e: spoon_errors.append(f"Spoonacular query failed ({term}): {e}") except Exception as e: spoon_errors.append(f"Unexpected Spoonacular query error ({term}): {e}") ``` ```python # scripts/core.py:349-350 except Exception as e: return _error(f"USDA query failed ...[truncated 2738 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Never return raw request exceptions to users** - Replace `str(e)` in user-facing errors and notes with fixed, provider-specific messages. - Preserve detailed diagnostics only in protected server-side logs after redaction. 2. **Handle every non-success response explicitly** - Replace the final `raise_for_status()` path with a sanitized custom exception: ```python def _handle_response(r: requests.Response): if r.status_code == 401: raise USDAError("USDA authentication failed", status=401) if r.status_code == 403: raise USDAError("USDA access was denied", status=403) if r.status_code == 429: raise USDAError("USDA rate limit reached", status=429) if r.status_code >= 500: raise USDAError("USDA service is temporarily unavailable", status=503) if not r.ok: raise USDAError( f"USDA request failed with HTTP status {r.status_code}", status=r.status_code, ) ``` 3. **Introduce centralized credential redaction** - Redact values associated with `api_key`, `apiKey`, `Authorization`, `token`, and similar fields before logging or returning exception data. - Apply redaction to both URL query strings and serialized request metadata. 4. **Avoid placing credentials in result notes** - Store only a provider name, sanitized status code, and stable internal error identifier. - Do not expose prepared request URLs, response bodies, headers, or exception representations. 5. **Add regression tests** - Mock unhandled responses such as HTTP 400, 404, and 422. - Assert that neither the USDA key nor the Spoonacular key appears in returned values, notes, exception messages, or captured logs. 6. **Consider safer authentication transport where supported** - If a provider supports authentication headers, prefer them over URL parameters. - Where query-parameter authentication is required by the provider, ensure URLs are always treat ...[truncated 66 chars]
