T09 · Insecure Skill Coding Practices
- Location
scripts/zoodata.py:64- Finding
Bearer Credential Can Be Transmitted over Plaintext HTTP
- Content
View full analysis
Vulnerability Details
File Location:
scripts/zoodata.py:64-86andscripts/zoodata.py:338-378
Vulnerability Type: Insufficient transport security validation
Risk Level: HighVulnerable Code
python def _is_trusted_host(url): """True only for ZooData hosts and localhost — the sole destinations the API key (Bearer token) may be sent to. Any other host is untrusted and the key is withheld (see api_call), so credentials never reach an arbitrary host.""" host = _host_of(url) return host == "zoodata.ai" or host.endswith(".zoodata.ai") or host in ("localhost", "127.0.0.1") def _resolve_base_url(): """Resolve API base URL, allowing zoodata.ai / localhost hosts via ZOODATA_BASE_URL.""" configured = os.environ.get("ZOODATA_BASE_URL", DEFAULT_BASE_URL).strip().rstrip("/") if configured.rstrip("/") != DEFAULT_BASE_URL.rstrip("/") and not _is_trusted_host(configured): print(f"WARNING: ZOODATA_BASE_URL points at untrusted host '{_host_of(configured)}'. " "Your API key (Bearer token) will NOT be sent there — requests to untrusted " "hosts are refused. Use a zoodata.ai host or localhost.", file=sys.stderr) if configured.endswith(API_BASE_PATH): return configured return f"{configured}{API_BASE_PATH}" BASE_URL = _resolve_base_url() BASE_URL_TRUSTED = _is_trusted_host(BASE_URL)python def api_call(endpoint: str, params: dict) -> dict: global _last_request_time url = f"{BASE_URL}/{endpoint}" if not BASE_URL_TRUSTED: print(f"ERROR: refusing to send your API key to untrusted host '{_host_of(BASE_URL)}'. " "Set ZOODATA_BASE_URL to a zoodata.ai host or localhost, or unset it.", file=sys.stderr) sys.exit(1) api_key = get_api_key() params = {k: v for k, v in params.items() if v is not None} for str_field in (" ...[truncated 3386 chars]- Remediation
View remediation
Remediation Suggestions
- Parse the configured URL with
urllib.parse.urlparse()and validate the scheme, hostname, port, username, and password fields together. - Require
httpsfor all ZooData domains before attaching theAuthorizationheader. - Prefer requiring HTTPS for loopback destinations as well.
- If plaintext loopback support is essential for development, disable it by default and require a separate explicit development-only opt-in, such as
ZOODATA_ALLOW_INSECURE_LOOPBACK=1. - Reject URLs containing embedded user information, fragments, malformed hosts, or unexpected schemes.
- Consider restricting production traffic to the exact expected hostname,
api.zoodata.ai, rather than trusting every*.zoodata.aisubdomain. - Construct the Bearer header only after all transport and destination checks succeed.
- Add automated tests proving that
http://api.zoodata.ai, plaintext loopback URLs, non-ZooData hosts, deceptive suffixes, and URLs containing embedded credentials are rejected without resolving or transmitting the API key.
A hardened validation pattern should enforce the transport policy explicitly:
python from urllib.parse import urlparse def _is_trusted_api_url(url): parsed = urlparse(url) host = (parsed.hostname or "").lower() if parsed.username or parsed.password: return False if parsed.scheme != "https": return False if host != "api.zoodata.ai": return False return True- Parse the configured URL with
