Back to skill

Security audit

Better Tavily Search

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Tavily search purpose, but an undocumented API endpoint override could send the Tavily API key and user queries to an unintended server.

Install only if you trust this skill with your Tavily API key and web-retrieval inputs. Avoid setting TAVILY_API_BASE unless you fully trust the endpoint, and prefer running it in an environment where outbound network access is limited to Tavily.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
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, and scripts/tavily.py:232-246
Vulnerability Type: Unvalidated credential destination and plaintext credential transmission
Risk Level: High

Vulnerable 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_BASE from the undocumented TAVILY_API_BASE environment 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: Bearer header. Consequently, setting TAVILY_API_BASE to an attacker-con ...[truncated 2373 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the TAVILY_API_BASE override if alternate API endpoints are not an explicit functional requirement:

    python
    API_BASE = "https://api.tavily.com"
    
  2. If endpoint customization is required, parse and validate the configured URL before loading or using the API key:

    • Require the https scheme.
    • 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.
  3. Do not attach the bearer credential to any destination whose origin differs from the approved Tavily API origin.

  4. Disable redirects or explicitly reject cross-origin redirects for authenticated requests. Authorization credentials must never be forwarded to a different host.

  5. Apply outbound network policy so the Skill can contact only approved Tavily API endpoints.

  6. Document any intentionally supported endpoint override, its security implications, and the trusted values permitted in production.

  7. 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("/")
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (8)

Tainted flow: 'req' from os.environ.get (line 241, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/tavily.py (reported line 246)May include surrounding context.

python
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:
                    body = resp.read().decode("utf-8", errors="replace")
                    try:
                        parsed = json.loads(body)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · reference/output_contract.md (reported line 165)May include surrounding context.

}

text

## Output Rules

- Keep the default machine-facing format stable across versions.
- Do not silently drop retrieval signals that help planning.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/tavily.py (reported line 179)May include surrounding context.

python
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:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/tavily.py (reported line 184)May include surrounding context.

python
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:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares executable capabilities that can access environment variables, local files, and the network, but it does not specify any explicit tool scope or permission boundary. That creates an unnecessary trust gap: an agent may invoke the skill with broader authority than intended, including access to secrets like TAVILY_API_KEY and arbitrary outbound requests.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 32)May include surrounding context.

md
Default pattern:
1. Run a small Tavily search with an intent-aligned profile.
2. Inspect titles, URLs, domains, snippets, dates, and scores.
3. Rewrite the query or refine Tavily parameters if the first pass is weak.
4. Extract content from the best 1–3 URLs only when more detail is needed.
5. Use site mapping only for documentation or site-navigation tasks.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code sends request payloads to an external Tavily API, and those payloads can contain user search queries, URLs to extract, and natural-language mapping instructions. Although the file's top-level docstring describes the CLI purpose, there is no explicit user-facing disclosure, confirmation, or warning near execution that this data is transmitted to a third-party service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The script reads a sensitive credential from the process environment or a local env file to authenticate outbound API requests. There is no visible warning, log message, or explanatory comment informing users that the skill accesses stored credentials as part of execution.

Content

No source excerpt is available for this finding.