Back to skill

Security audit

Tavily Search

Security checks for vulnerabilities and agentic risk

Overview

This Tavily search skill is mostly coherent, but its wrapper can turn a search query into local shell command execution, so it needs review before use.

Review or patch the wrapper before installing or invoking it with untrusted queries. The safer pattern is to call scripts/search.mjs directly or replace execSync string interpolation with execFileSync/spawnSync argument arrays and a minimal environment. Expect submitted queries, URLs, and the Tavily API key to be sent to Tavily's API.

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
openclaw-wrapper.js:1
Finding
OS Command Injection Through Untrusted Search Query## Vulnerability Details **File Location**: `openclaw-wrapper.js`, lines 1-12 **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js const { execSync } = require('child_process'); const query = process.argv[2] || ''; if (!query) { console.log('请提供搜索关键词'); process.exit(1); } try { const result = execSync(`node scripts/search.mjs "${query}" -n 5 --topic news`, { env: { ...process.env, TAVILY_API_KEY: process.env.TAVILY_API_KEY } }).toString(); ``` ### Technical Analysis The wrapper reads the search query directly from `process.argv[2]` and interpolates it into a command string passed to `child_process.execSync`. Because `execSync` executes the string through a shell, double quotes do not neutralize all shell syntax. An attacker can use command substitution or terminate the quoted argument and append another command. The child shell inherits the complete parent environment through `{ ...process.env }`, including `TAVILY_API_KEY`. Consequently, an injected process can access credentials and any other environment variables available to the wrapper. ### Attack Path 1. An attacker gains influence over the search query passed as the wrapper's second command-line argument. 2. The attacker supplies a query containing shell syntax, such as command substitution or a quote followed by a command separator. 3. The wrapper inserts that value into the command template without shell-safe argument handling. 4. `execSync` invokes a shell to interpret the resulting command. 5. The shell executes the attacker's injected command under the identity and permissions of the wrapper process. 6. The injected process can read inherited environment variables, access files available to the current user, modify accessible resources, or initiate network connections. ### Impact Assessment Successful exploitation provides arbitrary command execution with the privileges of the account ...[truncated 695 chars]
Remediation
## Remediation Suggestions Replace shell-based execution with `execFileSync` or `spawnSync`, pass every argument as a separate array element, and explicitly disable shell execution. Resolve the script relative to the wrapper's own directory so behavior does not depend on the current working directory. ```js const path = require('path'); const { execFileSync } = require('child_process'); const script = path.join(__dirname, 'scripts', 'search.mjs'); const result = execFileSync( process.execPath, [script, query, '-n', '5', '--topic', 'news'], { shell: false, encoding: 'utf8', env: { PATH: process.env.PATH, TAVILY_API_KEY: process.env.TAVILY_API_KEY } } ); console.log(result); ``` Additional hardening measures: - Avoid passing the entire parent environment to child processes. Supply only variables required by Node.js and the Tavily script. - Apply a reasonable query-length limit to reduce resource abuse, while not relying on input validation as the command-injection fix. - Use structured error handling that reports failure without exposing sensitive command, environment, or response details. - Add regression tests containing quotes, semicolons, command substitutions, newlines, and other shell metacharacters, verifying that they remain literal query content.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill invokes Node scripts that require both environment access (`TAVILY_API_KEY`) and outbound network access, but it does not declare any explicit tool scope such as `permissions` or `allowed-tools`. This creates an authorization gap where an agent or runtime may grant broader capabilities than users expect, increasing the risk of unintended secret exposure or unrestricted external requests.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code emits user-facing output only in Chinese ('请提供搜索关键词' and '搜索失败'), which imposes a specific language on users without any opt-in or documented justification. The policy explicitly flags forced language or locale behavior when no choice is offered.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
Instead of querying a search term and returning search results, the code takes arbitrary URLs as input and causes Tavily to fetch and extract raw page content from them. That capability is distinct from and broader than a concise web search function for AI agents.

External Transmission

Medium
Category
Data Exfiltration
Content
process.exit(1);
}

const resp = await fetch("https://api.tavily.com/extract", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
process.exit(1);
}

const resp = await fetch("https://api.tavily.com/extract", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
process.exit(1);
}

const resp = await fetch("https://api.tavily.com/extract", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The skill manifest says the skill provides AI-optimized web search returning relevant results, but this file calls Tavily's /extract endpoint on arbitrary user-supplied URLs and returns raw extracted page content. Content extraction is a materially different capability from search and goes beyond an obvious implementation detail of performing a search.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code sends user-supplied URLs and the TAVILY_API_KEY to an external API, which is a privacy- and credential-relevant operation. Although the script validates inputs and errors on missing credentials, it provides no confirmation prompt, warning comment, or user-facing disclosure that the URLs will be transmitted to Tavily.

External Transmission

Medium
Category
Data Exfiltration
Content
body.days = days;
}

const resp = await fetch("https://api.tavily.com/search", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
openclaw-wrapper.js:10