Back to skill

Security audit

smart-search

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to perform its advertised search and quota functions, but it persistently records raw searches and result summaries while broadly routing queries to external providers without enough user control.

Install only if you are comfortable with search terms and partial results being stored locally for up to 30 days and with queries being sent to third-party search/API providers. Avoid using it for secrets, private customer data, regulated content, or sensitive internal investigations unless logging is disabled or redacted and dependency installation is pinned/reviewed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:280
Finding
Concurrent quota updates allow provider limits to be exceeded<![CDATA[ ## Vulnerability Details **File Location**: `index.js:280-349`, `index.js:867-872`, and `index.js:897-899` **Vulnerability Type**: Race condition caused by non-atomic quota reservation and update **Risk Level**: Medium ### Vulnerable Code ```js // writes quota object to file with lock async function saveQuota(quota) { let release; try { release = await lockfile.lock(quotaPath, { retries: 3, minTimeout: 100, stale: 5000 }); } catch (_) { throw new Error('Quota file is locked. Try again.'); } try { fs.writeFileSync(quotaPath, JSON.stringify(quota, null, 2)); console.error('[quota] Saved.'); } finally { await release(); } } ``` ```js let quota = await loadQuota(); quota = resetIfNewDay(quota, config); quota = reconcileConfig(config, quota); await saveQuota(quota); ``` ```js if (result.updatedQuota) { await saveQuota(result.updatedQuota); delete result.updatedQuota; } ``` ### Technical Analysis The inter-process lock is acquired only while writing the final quota object. It does not protect the complete read-check-reserve-update transaction. A search operation performs the following actions outside a shared lock: 1. Reads the quota file. 2. Checks whether quota is available. 3. Calls the external provider. 4. Deducts quota in its private in-memory copy. 5. Acquires the lock only to overwrite the quota file. Consequently, multiple processes can read the same initial state and independently conclude that quota is available. Each process can then make an API request. Their final writes are serialized, but they contain stale snapshots, so a later write can overwrite counters written by an earlier process. This is a classic lost-update race. Locking only the write operation does not make the surrounding read-modify-write sequence atomic. ### Attack Path 1. Configure a provider with one or a small number of remaining calls. 2. Submit multiple `smart_search` invocations concurrently using separate Skill processe ...[truncated 1103 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Protect the entire quota reservation transaction with one inter-process lock: 1. Acquire the quota-file lock. 2. Read and validate the latest quota state while holding the lock. 3. Reconcile configuration and check availability. 4. Reserve or deduct one quota unit. 5. Atomically persist the updated state. 6. Release the lock. - Reserve quota before making the provider request. If the provider request fails, reacquire the lock and refund the reservation where appropriate. - Write to a temporary file in the same directory and atomically rename it over the quota file to prevent partial writes. - Add a request or reservation identifier so retries and refunds can be made idempotent. - Add concurrency tests that launch multiple processes against a quota of one and verify that no more than one provider call is authorized. - Consider replacing file-based accounting with a transactional store if high concurrency is expected. ]]>

T08 · Insecure Dependencies

Note
Location
package.json:6
Finding
Unpinned dependency installation creates a mutable supply-chain boundary<![CDATA[ ## Vulnerability Details **File Location**: `package.json:6-8` and `scripts/setup.sh:30-33` **Vulnerability Type**: Non-reproducible third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```json "dependencies": { "proper-lockfile": "^4.1.2" } ``` ```bash # ── 2. Install dependencies ─────────────────────────────────────────────────── echo "[setup] Installing npm dependencies..." cd "$SKILL_DIR" npm install --silent echo "[setup] Dependencies installed." ``` ### Technical Analysis The project declares `proper-lockfile` using the mutable semver range `^4.1.2` and does not include a package lockfile. The setup script then runs `npm install`, which resolves the dependency graph at installation time. This means the installed code is not necessarily the exact code reviewed during the audit. Future versions accepted by the caret range, or changed transitive dependency resolutions, can be installed without changes to this Skill package. In addition, ordinary `npm install` permits package lifecycle scripts unless separately disabled. If a newly resolved direct or transitive package were compromised, its lifecycle script could execute with the privileges of the user running `scripts/setup.sh`. No currently malicious dependency or lifecycle script was identified in the audited files. The finding is the unsafe and non-reproducible installation process rather than evidence that `proper-lockfile` itself is malicious. ### Attack Path 1. A future dependency version compatible with `^4.1.2`, or one of its transitive dependencies, is compromised or publishes unsafe lifecycle behavior. 2. A user runs `scripts/setup.sh`. 3. The script invokes `npm install --silent` without a committed lockfile. 4. npm resolves the mutable dependency graph and downloads the affected package. 5. Any permitted installation lifecycle script executes as the user running setup. 6. Compromised runtime code can also execute later when `index.js` imports `proper ...[truncated 665 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate, review, and commit `package-lock.json`. - Replace `npm install` in setup with `npm ci`, which installs the dependency graph recorded in the lockfile. - Pin the direct dependency to an exact reviewed version rather than a caret range where feasible. - Review transitive dependencies and periodically audit them with appropriate package-security tooling. - Use `npm ci --ignore-scripts` if dependency lifecycle scripts are not required. - If lifecycle scripts are required, explicitly document and review every package that uses them. - Avoid suppressing all npm output with `--silent` during security-sensitive installation, or preserve installation logs for troubleshooting and review. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented behavior claims search routing, API use, circuit breaking, and fallback logic, while the analyzed behavior reportedly performs direct quota-file modification and backup instead. That mismatch is security-relevant because operators may approve the skill for low-risk web search while it actually changes persistent local state, creating opportunity for unauthorized quota resets, audit evasion, or policy bypass.

Ssd 3

High
Confidence
98% confidence
Finding
The examples and log schema explicitly state that manual chat searches are written to daily JSONL files with raw query content and response summaries. Manual/chat queries are especially likely to contain sensitive conversational content, so persisting them to disk substantially raises the risk of local data leakage, unauthorized access by other users/processes, and long-tail compliance issues.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill documentation advertises capabilities that rely on network access and environment-held API keys, but it does not declare an explicit tool scope or permission boundary. In an agent ecosystem, that weakens least-privilege controls and makes it easier for the skill to be granted broader access than users or operators expect.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill states that it logs all searches but does not provide a clear, prominent privacy warning at the point of use. Users or calling agents may unknowingly send sensitive prompts, identifiers, or business data into a system that persists them locally, which creates avoidable confidentiality and compliance risk.

Ssd 3

Medium
Confidence
93% confidence
Finding
Persistent logging of raw search queries and response summaries creates a durable record of natural-language inputs that may contain personal data, secrets, internal research, or regulated content. Because this skill is intended for broad web-search use, the likelihood of collecting sensitive user-entered text is materially higher than in a narrowly scoped operational tool.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The invocation guidance says to use the skill for essentially any web search need, which can cause over-triggering by agents and unnecessary transmission/logging of user queries. In this skill, that broad routing advice is more dangerous because searches may hit external providers and are also persisted to daily logs, increasing privacy and data-exposure risk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
User search queries are transmitted to third-party services such as Gemini, Brave, and web search engines, which can expose sensitive user intent or embedded confidential data outside the local environment. While external transmission is inherent to a search skill, the absence of disclosure or controls makes this a real privacy/security concern in agent workflows.

External Transmission

Medium
Category
Data Exfiltration
Content
}

  const encoded = encodeURIComponent(query);
  const endpoint = `https://api.search.brave.com/res/v1/web/search?q=${encoded}`;

  let response;
  try {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The skill persistently logs raw queries and response summaries to local JSONL files, which can capture sensitive user prompts, searched entities, and provider-derived content unrelated to core routing. In an agent environment, search queries often contain confidential investigation context, so local persistence increases exposure to other local users, backup systems, or later compromise.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The manifest frames the skill as API-based routing across Gemini and Brave with web_fetch as a fallback, but the general search path is implemented with Google and Bing scraping as the primary behavior. This is a semantic mismatch because a major execution path does something materially different from the described routing model.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The main search flow records query text, provider selection, errors, and response summaries without any user-facing notice or consent mechanism in this code path. Because this occurs on every search, it creates a systematic privacy leak and retention risk, especially for sensitive agent tasks.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation states that corrupted JSON is deleted and replaced with a fresh default file, which is destructive recovery behavior that can silently erase quota/accounting state. In this skill's context, that can invalidate usage tracking, reset limits unexpectedly, and undermine quota enforcement or auditability if corruption is triggered intentionally or occurs during concurrent access.

Intent-Code Divergence

Low
Confidence
94% confidence
Finding
The parameter documentation at L052 and the invalid-provider error at L277 state that only `gemini` or `brave` are valid values for `force_provider`. Immediately after, L278 documents a separate `Perplexity is not yet implemented` error path, which implies support or recognition for an undeclared provider and contradicts the stated interface.

Description-Behavior Mismatch

Low
Confidence
76% confidence
Finding
The manifest mentions a web_fetch fallback without describing that the code uses specific third-party search engines, including DuckDuckGo, as part of the fallback logic. While still related to search, the actual behavior is broader and more specific than the declared description.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"description": "OpenClaw AgentSkill for intelligent search routing with quota management",
  "main": "index.js",
  "dependencies": {
    "proper-lockfile": "^4.1.2"
  },
  "scripts": {
    "start": "node index.js",
Confidence
95% confidence
Finding
The dependency is specified with a caret range (^4.1.2), which permits automatic installation of newer minor and patch releases. That weakens supply-chain reproducibility and can unexpectedly pull in a compromised or breaking upstream version if the lockfile is absent, regenerated, or ignored in deployment.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
index.js:22