Back to skill

Security audit

Unified Web Search (Iyeque)

Security checks for vulnerabilities and agentic risk

Overview

This search skill is mostly purpose-aligned, but it has review-worthy gaps: it overstates supported sources, can expose local file path metadata, and passes the full process environment to an external sibling script.

Install only if you are comfortable with queries being sent to Tavily and with local filename/path metadata being returned from OpenClaw workspace memory and skills directories. Avoid running it in an environment containing unrelated secrets until it passes only the required Tavily key to child processes, uses canonical path checks for local search, and updates its documentation to match the implemented sources.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
index.js:96
Finding
Workspace Boundary Bypass Through Symbolic-Link Search Directories<![CDATA[ ## Vulnerability Details **File Location**: `index.js:96-109` **Vulnerability Type**: Improper filesystem boundary validation involving symbolic links **Risk Level**: Medium ### Vulnerable Code ```js const allowedSubdirs = ['memory', 'skills']; // Do not include '.' to prevent scanning root if it has sensitive files for (const subdir of allowedSubdirs) { const searchPath = path.join(workspaceRoot, subdir); if (!fs.existsSync(searchPath)) continue; // Additional safety check: ensure searchPath is actually inside workspaceRoot if (!path.resolve(searchPath).startsWith(workspaceRoot)) continue; const files = fs.readdirSync(searchPath, { withFileTypes: true }); for (const file of files) { if (file.isFile() && file.name.toLowerCase().includes(sanitizedQuery.toLowerCase())) { ``` ### Technical Analysis The containment check uses `path.resolve(searchPath)`, which performs lexical path normalization but does not resolve symbolic links. Because `searchPath` is constructed from `workspaceRoot` and a fixed subdirectory name, its normalized string will appear to be inside the workspace even when `memory` or `skills` is a symbolic link pointing to an external directory. `fs.existsSync()` and `fs.readdirSync()` follow a symbolic link used as the directory being inspected. Consequently, a link such as `~/.openclaw/workspace/memory -> /sensitive/directory` passes the existing check, after which the external directory is enumerated. The code only tests immediate entries with `file.isFile()` and does not read their contents. The direct exposure is therefore limited to matching filenames and generated path metadata, rather than file contents. ### Attack Path 1. An attacker who can modify the workspace creates or replaces an allowed search directory with a symbolic link: ```bash ln -s /sensitive/directory ~/.openclaw/workspace/memory ``` 2. The attacker invokes the Skill with the `local` source and a likely filename fragment: ```bas ...[truncated 901 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Resolve canonical filesystem paths before performing directory operations: 1. Resolve `workspaceRoot` with `fs.realpathSync()` after verifying that it exists. 2. Resolve each candidate directory with `fs.realpathSync()` so symbolic links are expanded. 3. Use `path.relative()` rather than a raw string-prefix comparison to verify containment. 4. Explicitly reject an allowed search root when `fs.lstatSync(searchPath).isSymbolicLink()` is true. 5. Open or enumerate the canonical path only after validation. 6. Handle race conditions where a path may be replaced between validation and use. Where practical, operate through trusted directory handles or ensure the workspace is not writable by untrusted users. Example hardening pattern: ```js const canonicalRoot = fs.realpathSync(workspaceRoot); const candidatePath = path.join(canonicalRoot, subdir); if (fs.lstatSync(candidatePath).isSymbolicLink()) { continue; } const canonicalSearchPath = fs.realpathSync(candidatePath); const relative = path.relative(canonicalRoot, canonicalSearchPath); if (relative === '' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) { continue; } const files = fs.readdirSync(canonicalSearchPath, { withFileTypes: true }); ``` The check should be covered by tests in which both `memory` and `skills` are symbolic links to locations outside the workspace. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:52
Finding
Complete Process Environment Exposed to a Sibling Search Script<![CDATA[ ## Vulnerability Details **File Location**: `index.js:52-62` **Vulnerability Type**: Excessive propagation of environment secrets to a child process **Risk Level**: Medium ### Vulnerable Code ```js const scriptPath = path.join(__dirname, '../tavily-search/scripts/search.mjs'); const apiKey = process.env.TAVILY_API_KEY || ''; if (!apiKey) throw new Error('TAVILY_API_KEY not set'); try { // safeQuery has quotes stripped, so wrapping in "" is safe const cmd = `node "${scriptPath}" "${sanitizedQuery}" -n ${params.limit || 5}`; const output = execSync(cmd, { env: { ...process.env, TAVILY_API_KEY: apiKey }, encoding: 'utf8', ``` ### Technical Analysis The child process is explicitly given a copy of the entire parent environment through `{ ...process.env }`. The invoked program is not part of this audited project; it is a sibling script located at `../tavily-search/scripts/search.mjs`. Although the integration only requires `TAVILY_API_KEY`, the sibling process can access every environment variable held by the parent. Depending on the execution environment, these variables may include cloud credentials, repository tokens, database passwords, signing keys, session tokens, proxy credentials, or keys belonging to unrelated Skills. This violates least privilege and increases the impact of a compromised, replaced, or otherwise untrusted sibling script. The code does not itself exfiltrate these values, but it unnecessarily makes them available to another executable component. The command is also constructed as a shell string. The current query sanitizer reduces direct query-based injection risk, but `execFileSync()` with an argument array would remove reliance on shell parsing and provide stronger defense in depth. ### Attack Path 1. An attacker compromises, replaces, or gains control over `../tavily-search/scripts/search.mjs`. 2. A user invokes this Skill with Tavily enabled while the parent process contains unrelated secrets in environmen ...[truncated 957 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pass a minimal, explicit environment allowlist to the child process instead of copying `process.env`. Include only variables required to locate Node.js and authenticate to Tavily. Use `execFileSync()` with separate arguments to avoid invoking a shell: ```js const { execFileSync } = require('child_process'); const childEnv = { PATH: process.env.PATH, TAVILY_API_KEY: apiKey }; const output = execFileSync( process.execPath, [scriptPath, sanitizedQuery, '-n', String(params.limit || 5)], { env: childEnv, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] } ); ``` If the child requires additional runtime variables, add each one only after documenting why it is needed. Also: - Verify the identity and integrity of the sibling component before execution. - Restrict write permissions on the sibling script and its parent directory. - Avoid placing unrelated secrets in the parent environment when invoking Skills. - Consider running external Skill integrations in an isolated process or sandbox with restricted filesystem and network access. ]]>
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 (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill advertises automatic source selection, browser support, and broader local/web search behavior that are not actually implemented. This mismatch can mislead users and downstream agents into sending sensitive queries externally or relying on incomplete results under false assumptions about provenance, coverage, and locality of processing.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill declares access to environment variables via metadata (`TAVILY_API_KEY`) but does not define an explicit tool scope such as `permissions` or `allowed-tools`. That weakens least-privilege guarantees and makes the skill's effective capabilities less transparent to operators, increasing the risk of unintended data access or unsafe execution in environments that rely on manifest-declared boundaries.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill routes queries to third-party search providers but does not present a prominent user warning that submitted queries may leave the local environment. In this context, users may enter confidential project names, file hints, or internal research topics, causing inadvertent disclosure to external services.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest says the skill will pick among Tavily, Web Search Plus, Browser, or local files, but the implementation defaults only to 'tavily', 'web-search-plus', and 'local' and later actually executes only Tavily and local search paths. There is no Browser implementation, and 'web-search-plus' is never used, so the described source-selection behavior does not match the real code.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The skill is presented as a unified web search router, but it also enumerates local workspace files and returns their paths and matching filenames. In an agent setting, this can expose internal workspace structure and file names to users who only intended a web search, creating an unexpected information disclosure channel across trust boundaries.