Back to skill

Security audit

Web Search Plus

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real web search/extraction skill, but it needs Review because API keys can be stored or sent more broadly than the skill's own permission text suggests.

Review this skill before installing if you will use paid provider keys. Prefer environment variables or a protected secrets store over the setup wizard's config.json storage, keep config.json owner-readable only, do not configure custom provider API endpoints unless you trust them, and use explicit providers or self-hosted SearXNG for sensitive searches. Disable or clear the cache for sensitive queries.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/search.py:2198
Finding
Provider API credentials can be transmitted to arbitrary configured endpoints## Vulnerability Details **File Location**: `scripts/search.py:2198-2249`, `scripts/search.py:3328-3355`, `scripts/search.py:3694-3802` **Vulnerability Type**: Unvalidated credential-bearing provider endpoint **Risk Level**: Medium ### Vulnerable Code ```python def search_querit( query: str, api_key: str, max_results: int = 5, language: str = "en", country: str = "us", time_range: Optional[str] = None, include_domains: Optional[List[str]] = None, exclude_domains: Optional[List[str]] = None, base_url: str = "https://api.querit.ai", base_path: str = "/v1/search", timeout: int = 30, ) -> dict: endpoint = base_url.rstrip("/") + base_path # ... headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } data = make_request(endpoint, headers, body, timeout=timeout) ``` The endpoint is also configurable through a command-line argument: ```python querit_config = config.get("querit", {}) parser.add_argument( "--querit-base-url", default=querit_config.get("base_url", "https://api.querit.ai"), help="Querit API base URL" ) parser.add_argument( "--querit-base-path", default=querit_config.get("base_path", "/v1/search"), help="Querit API path" ) ``` Other credential-bearing provider endpoints are similarly read from configuration without validating their origins: ```python api_url=linkup_config.get("api_url", "https://api.linkup.so/v1/search") api_url=firecrawl_config.get("api_url", "https://api.firecrawl.dev/v2/search") api_url=perplexity_config.get( "api_url", "https://api.kilo.ai/api/gateway/chat/completions" ) api_url=serpbase_config.get("api_url", "https://api.serpbase.com/search") api_url=keenable_config.get("api_url", "https://api.keenable.ai/v1/search") ``` ### Technical Analysis Provider API keys are attached to HTT ...[truncated 2475 chars]
Remediation
## Remediation Suggestions 1. Define each provider's canonical HTTPS origin in `scripts/provider_registry.py` and construct request URLs from that trusted registry. 2. Before attaching credentials, require: - An `https` scheme. - No URL user-information component. - An exact hostname match against the provider's approved host. - An approved port, normally 443. 3. Remove `--querit-base-url` from ordinary runtime arguments, or require an explicit high-friction development option before accepting a custom origin. 4. If compatible private gateways must be supported, maintain a separate operator-controlled allowlist and clearly warn that provider credentials will be sent to the custom host. 5. Ensure authorization headers and credential-bearing request bodies are never forwarded to an unapproved redirect destination. 6. Add tests proving that HTTP URLs, metadata addresses, private destinations, look-alike domains, and arbitrary public hosts are rejected for credential-bearing provider requests. 7. Update the permission declaration if custom provider endpoints are intentionally retained; otherwise enforce the currently declared host restrictions in code.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.py:398
Finding
Setup wizard persists plaintext API keys without enforcing owner-only permissions## Vulnerability Details **File Location**: `scripts/setup.py:398-400`, `scripts/setup.py:461` **Vulnerability Type**: Insecure plaintext credential storage **Risk Level**: Medium ### Vulnerable Code The setup wizard stores entered credentials in the configuration object: ```python api_key = ask_api_key(name, url) if api_key: config[provider]["api_key"] = api_key enabled_providers.append(provider) ``` It later writes the configuration using process-default file permissions: ```python with open(config_path, 'w') as f: json.dump(config, f, indent=2) ``` ### Technical Analysis The recommended interactive setup collects provider API keys and writes them in plaintext to `config.json`. The file is created using ordinary `open()` without explicitly applying mode `0600`, securing the parent directory, checking for symbolic links, or using an atomic owner-only temporary file. The resulting permissions depend on the process umask and the permissions of the containing directory. In a shared workspace, permissively configured system, mounted volume, or multi-user development environment, another local account or process may be able to read the credentials. This behavior also conflicts with statements in `SKILL.md` and `README.md` that API keys are never persisted. Although the runtime cache code applies restrictive permissions, the setup wizard does not apply equivalent protections to the more sensitive `config.json` file. ### Attack Path 1. A user follows the recommended setup procedure: ```text python3 scripts/setup.py ``` 2. The user enters one or more valid provider API keys. 3. The wizard stores those keys in plaintext inside `config.json`. 4. The file is created with permissions determined by the current umask and directory environment. 5. On a system where those permissions permit access, another local user, container process, CI job, or compromised process reads `config.json`. ...[truncated 787 chars]
Remediation
## Remediation Suggestions 1. Prefer environment variables or an operating-system credential store instead of writing API keys to `config.json`. 2. If file-backed credentials remain supported: - Create the parent directory with mode `0700`. - Create the destination or temporary file with mode `0600`. - Write atomically and replace the destination only after a successful flush. - Reject symbolic-link destinations and avoid following existing symlinks. - Reapply mode `0600` to existing configuration files before writing. 3. Separate non-sensitive routing settings from credentials so that `config.json` can remain shareable while secrets reside in a protected credential file. 4. Warn users clearly that the setup wizard persists plaintext credentials locally. 5. Correct documentation stating that keys are never persisted, or change implementation behavior so that the statement becomes accurate. 6. Add automated tests that verify owner-only permissions and symlink-safe behavior for newly created and updated credential files. 7. Provide a command to migrate existing plaintext keys to environment variables or a protected secret store and securely remove them from `config.json`.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (69)

Credential Access

High
Category
Privilege Escalation
Content
## Quick start

```bash
cp .env.example .env
# fill in at least one key or SEARXNG_INSTANCE_URL

python3 scripts/search.py -q "latest OpenClaw release"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## Quick start

```bash
cp .env.example .env
# fill in at least one key or SEARXNG_INSTANCE_URL

python3 scripts/search.py -q "latest OpenClaw release"
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _load_env_file() -> None:
    env_paths = [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]
    for env_path in env_paths:
        if not env_path.exists():
            continue
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _load_env_file() -> None:
    env_paths = [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]
    for env_path in env_paths:
        if not env_path.exists():
            continue
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _load_env_file() -> None:
    env_paths = [Path(__file__).parent.parent / ".env", Path(__file__).parent / ".env"]
    for env_path in env_paths:
        if not env_path.exists():
            continue
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Test Auto-Routing Feature
# Tests various query types to verify routing works correctly

# Load from environment or .env file
if [ -f .env ]; then
  source .env
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
class SsrfValidationTests(unittest.TestCase):
    def test_non_http_scheme_rejected(self):
        for url in ("ftp://example.com/file", "file:///etc/passwd", "gopher://example.com"):
            with self.assertRaises(ValueError):
                url_security.validate_outbound_url(url)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
url_security.validate_outbound_url(f"http://{host}/path")

    def test_metadata_endpoints_blocked_even_with_private_opt_in(self):
        for host in ("169.254.169.254", "metadata.google.internal"):
            with self.assertRaises(ValueError, msg=host):
                url_security.validate_outbound_url(f"http://{host}/latest", allow_private=True)
            with mock.patch.dict(os.environ, {"WSP_ALLOW_PRIVATE_URLS": "1"}):
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Content
url_security.validate_outbound_url(f"http://{host}/path")

    def test_metadata_endpoints_blocked_even_with_private_opt_in(self):
        for host in ("169.254.169.254", "metadata.google.internal"):
            with self.assertRaises(ValueError, msg=host):
                url_security.validate_outbound_url(f"http://{host}/latest", allow_private=True)
            with mock.patch.dict(os.environ, {"WSP_ALLOW_PRIVATE_URLS": "1"}):
Confidence
90% confidence
Finding
Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

External Transmission

Medium
Category
Data Exfiltration
Content
[Querit.ai](https://querit.ai) is a Singapore-based multilingual AI search API purpose-built for LLMs and RAG pipelines. 300 billion page index, 20+ countries, 10+ languages.

- Added **Querit** as the 7th search provider via `https://api.querit.ai/v1/search`
- Configure via `QUERIT_API_KEY` — optional, gracefully skipped if not set
- Routing score: `research * 0.65 + rag * 0.35 + recency * 0.45` — favored for multilingual and real-time queries
- Handles Querit's quirky `error_code=200` responses as success (not an error)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
#### Features
- **Privacy-Preserving**: No tracking, no profiling — your searches stay private
- **Multi-Source Aggregation**: Queries 70+ upstream engines (Google, Bing, DuckDuckGo, etc.)
- **$0 API Cost**: Self-hosted = unlimited queries with no API fees
- **Diverse Results**: Get perspectives from multiple search engines in one query
- **Customizable**: Choose which engines to use, set SafeSearch levels, language preferences
Confidence
80% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- Searches work but don't cache

**Solutions:**
1. Check directory permissions: `chmod 755 .cache/`
2. Use custom cache dir: `export WSP_CACHE_DIR="$TMP_DIR/wsp-cache"`

---
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This config sets provider language defaults to "en" for Serper, Brave, and SearXNG, which is a natural-language locale constraint. The file does include an optional global locale override later, but these provider-specific defaults still force English behavior unless changed, with no explicit user opt-in at those settings.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The Brave configuration sets "search_lang" to "en", imposing a specific language choice. Under the policy, forcing a language without user choice or clearly justified regional scope is a violation.

Static analysis

No suspicious patterns detected.