T09 · Insecure Skill Coding Practices
- Location
scripts/tavily.py:29- Finding
Arbitrary API Base Override Can Exfiltrate the Tavily Bearer Credential
- Content
View full analysis
Vulnerability Details
File Location:
scripts/tavily.py:29,scripts/tavily.py:174-184, andscripts/tavily.py:232-246
Vulnerability Type: Unvalidated credential destination and plaintext credential transmission
Risk Level: HighVulnerable Code
python API_BASE = os.environ.get("TAVILY_API_BASE", "https://api.tavily.com").rstrip("/")python def load_api_key() -> str: env_value = (os.environ.get("TAVILY_API_KEY") or "").strip() if env_value: return env_value env_file = Path.home() / ".openclaw" / ".env" file_vars = read_env_file(env_file) env_value = (file_vars.get("TAVILY_API_KEY") or "").strip() if env_value: return env_value fatal("Missing TAVILY_API_KEY. Set the environment variable or add it to ~/.openclaw/.env", exit_code=1)python def post(self, endpoint: str, payload: dict[str, Any], *, timeout: float | None = None) -> dict[str, Any]: url = f"{API_BASE}/{endpoint.lstrip('/')}" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "better-tavily-search/0.2", } data = json.dumps(payload).encode("utf-8") req = urllib_request.Request(url, data=data, headers=headers, method="POST") last_exc: Exception | None = None for attempt in range(self.retries + 1): try: with urllib_request.urlopen(req, timeout=timeout or self.timeout) as resp:Technical Analysis
The script obtains
API_BASEfrom the undocumentedTAVILY_API_BASEenvironment variable but does not validate its scheme, hostname, port, or origin. The value is used directly to construct the destination of every API request.The same request unconditionally includes the Tavily API key in an
Authorization: Bearerheader. Consequently, settingTAVILY_API_BASEto an attacker-con ...[truncated 2373 chars]- Remediation
View remediation
Remediation Suggestions
-
Remove the
TAVILY_API_BASEoverride if alternate API endpoints are not an explicit functional requirement:python API_BASE = "https://api.tavily.com" -
If endpoint customization is required, parse and validate the configured URL before loading or using the API key:
- Require the
httpsscheme. - Require an explicit allowlist of trusted hostnames.
- Reject embedded usernames or passwords.
- Reject unexpected ports.
- Reject URL fragments and malformed origins.
- Normalize the hostname before comparison.
- Require the
-
Do not attach the bearer credential to any destination whose origin differs from the approved Tavily API origin.
-
Disable redirects or explicitly reject cross-origin redirects for authenticated requests. Authorization credentials must never be forwarded to a different host.
-
Apply outbound network policy so the Skill can contact only approved Tavily API endpoints.
-
Document any intentionally supported endpoint override, its security implications, and the trusted values permitted in production.
-
After suspected exploitation, immediately revoke and rotate the affected Tavily API key and review Tavily usage records for unauthorized activity.
A hardened validation pattern should resemble:
python from urllib.parse import urlparse TRUSTED_API_HOSTS = {"api.tavily.com"} def validated_api_base(value: str) -> str: parsed = urlparse(value) if parsed.scheme != "https": fatal("Tavily API base must use HTTPS") if parsed.hostname not in TRUSTED_API_HOSTS: fatal("Untrusted Tavily API host") if parsed.username or parsed.password: fatal("Credentials are not allowed in the Tavily API base") if parsed.port not in {None, 443}: fatal("Unexpected Tavily API port") return value.rstrip("/")-
