Back to skill

Security audit

yandex-image-search

Security checks for vulnerabilities and agentic risk

Overview

The skill generally does reverse image search as advertised, but it can upload any readable local path to third-party search providers without validating that it is an image.

Review this before installing if you expect the agent to handle private files. Only use local image files you intentionally want to send to Yandex, Google Lens, or Bing, avoid passing arbitrary paths, and consider requiring confirmation for local uploads. The publisher should add file validation, path scoping, pinned dependencies, and safer private debug logging before this is treated as low-risk.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/search.py:32
Finding

Arbitrary Local Files Can Be Uploaded to External Search Providers

Content
View full analysis

Vulnerability Details

File Location: scripts/search.py:32-35, scripts/search.py:150-156, and scripts/search.py:223-224
Vulnerability Type: Unrestricted local-file handling and external data disclosure
Risk Level: High

Vulnerable Code

python
def is_local_file(path: str) -> bool:
    if path.startswith(("http://", "https://", "ftp://")):
        return False
    return Path(path).exists()
python
if local:
    resp = await engine._send_request(
        method="post",
        params=params,
        data={"prg": 1},
        files={"upfile": read_file(input_path)},
    )
python
if is_local_file(input_path):
    resp = await engine.search(file=input_path)
else:
    resp = await engine.search(url=input_path)

Technical Analysis

The application considers any existing filesystem path to be a valid local image. It does not verify that the path:

  • Refers to a regular file rather than another filesystem object.
  • Resides within an explicitly approved upload directory.
  • Has a permitted image format or MIME type.
  • Can be decoded as an image.
  • Satisfies a safe file-size limit.
  • Does not contain sensitive or polyglot content.

Consequently, any file readable by the process can be passed to engine.search(file=...) and uploaded to Yandex, Google Lens, or Bing. The Yandex diagnostic fallback can perform an additional direct upload using read_file(input_path).

This is particularly dangerous if an untrusted party can influence the command-line argument, such as through an agent-generated invocation, automation wrapper, API parameter, or crafted instruction.

Attack Path

  1. An attacker influences the value supplied as image_url_or_path.
  2. The attacker supplies the path of a sensitive readable file, such as an environment file, application configuration, source file, access token file, or SSH private key.
  3. Path(path).exists() returns ...[truncated 926 chars]
Remediation
View remediation

Remediation Suggestions

  • Resolve the path with Path.resolve(strict=True) and require it to remain inside one or more explicitly approved upload directories.
  • Require Path.is_file() and reject symbolic links, devices, directories, sockets, and other special filesystem objects.
  • Enforce a conservative maximum file size before reading or uploading content.
  • Decode the file with a trusted image-processing library and only accept an allowlist of raster formats such as JPEG, PNG, and WebP.
  • Do not rely solely on extensions or client-provided MIME types.
  • Reject active or complex formats such as SVG unless they are safely rasterized in isolation.
  • Require explicit user confirmation identifying the destination provider before uploading local content.
  • Avoid repeating an upload during diagnostics unless the user has separately authorized it.
  • Prefer accepting an already validated attachment object rather than an unrestricted filesystem path.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/search.py:68
Finding

Predictable Temporary Filename Allows Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/search.py:68-73, invoked at scripts/search.py:167
Vulnerability Type: Unsafe temporary-file creation
Risk Level: Medium

Vulnerable Code

python
def save_debug_html(prefix: str, base_url: str, html: str) -> str:
    ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    host = base_url.replace("https://", "").replace("http://", "").replace("/", "_")
    out = f"/tmp/{prefix}-{host}-{ts}.html"
    Path(out).write_text(html, encoding="utf-8")
    return out
python
html_path = save_debug_html("openclaw-yandex-debug", base_url, html)

Technical Analysis

The diagnostic HTML filename is constructed from public constants, the known Yandex hostname, and a timestamp with only one-second precision. It is therefore predictable.

/tmp is normally writable by other local users. Path.write_text() opens the path for writing without atomically requiring creation of a new regular file and follows an existing symbolic link. A local attacker can pre-create a symbolic link using the predicted filename and redirect the write to another file writable by the skill process.

The implementation also stores complete remote HTML responses in a shared temporary directory without explicitly assigning restrictive permissions or cleaning up the files.

Attack Path

  1. A local attacker observes or predicts when Yandex diagnostics will run.
  2. The attacker calculates a candidate filename such as /tmp/openclaw-yandex-debug-yandex.com-YYYYMMDDTHHMMSSZ.html.
  3. Before the diagnostic write occurs, the attacker creates that pathname as a symbolic link to a target file writable by the victim process.
  4. A Yandex hard failure triggers save_debug_html().
  5. Path.write_text() follows the attacker-created symbolic link.
  6. The target file is truncated and replaced with the received HTML response.

Exploitation requires local access to the same ...[truncated 626 chars]

Remediation
View remediation

Remediation Suggestions

  • Use Python's tempfile.NamedTemporaryFile or tempfile.mkstemp to create an unpredictable file atomically.
  • Create temporary files with owner-only permissions, such as mode 0600.
  • Do not reopen a generated pathname in a way that reintroduces a time-of-check/time-of-use race.
  • Store diagnostics in a private per-user or per-process directory with mode 0700.
  • Delete diagnostic files promptly after use.
  • Disable full-response retention by default and require an explicit debug option to enable it.
  • Consider retaining only non-sensitive metadata instead of complete HTML responses.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:15
Finding

Unpinned Runtime Dependencies Create a Supply-Chain Execution Risk

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:15-19
Vulnerability Type: Unpinned third-party dependency installation
Risk Level: Medium

Vulnerable Code

bash
SKILL_DIR="$(dirname "SKILL.md")"
python3 -m venv "$SKILL_DIR/scripts/.venv"
"$SKILL_DIR/scripts/.venv/bin/pip" install -q PicImageSearch typing_extensions

Technical Analysis

The setup procedure installs PicImageSearch and typing_extensions without pinning audited versions or validating package hashes. The code installed during future setup operations can therefore differ from the versions reviewed during this audit.

Package resolution also depends on the user's pip configuration and selected package indexes. A compromised package release, compromised index, unsafe additional index, or malicious dependency selected during transitive resolution could introduce attacker-controlled code.

Python packages may execute code during build or installation, and their modules execute when imported by scripts/search.py. As a result, dependency compromise can lead directly to code execution in the context of the user running the setup or skill.

Attack Path

  1. An attacker compromises an upstream package or one of its transitive dependencies, or causes pip to resolve a malicious package through an unsafe configured index.
  2. A user follows the documented setup instructions.
  3. Pip resolves the latest available dependency versions because no exact versions or hashes are specified.
  4. Malicious build, installation, or package code is installed into scripts/.venv.
  5. The malicious code executes during installation or when PicImageSearch is imported by the search script.
  6. The code runs with the operating-system privileges and data access of the invoking user.

Impact Assessment

A compromised dependency can obtain arbitrary code execution with the privileges of the user running the installation or skill. It may access files, ...[truncated 321 chars]

Remediation
View remediation

Remediation Suggestions

  • Pin direct and transitive dependencies to exact, reviewed versions.
  • Generate and commit a lock file suitable for the supported Python environment.
  • Record cryptographic hashes for every accepted distribution.
  • Install with pip --require-hashes from the locked requirements file.
  • Use an explicitly configured trusted package index rather than inheriting arbitrary user index settings.
  • Prefer pre-reviewed binary wheels where appropriate and disable unnecessary source builds.
  • Add automated dependency vulnerability and integrity scanning.
  • Review and deliberately update the lock file rather than resolving the newest packages during ordinary setup.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
SKILL_DIR="$(dirname "SKILL.md")"

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
SKILL_DIR="$(dirname "SKILL.md")"

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill instructs the agent to create a virtual environment, install packages, and invoke a Python script on either a URL or a local file path, which implies file read and file write behavior. Because the manifest does not declare any tool scope or permissions, consumers cannot accurately constrain or review these capabilities, increasing the risk of unintended local file access or filesystem modification during execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

When the input is a local file, the script sends the image contents to external reverse-image-search providers such as Yandex, Google Lens, or Bing. In the context of a skill that accepts local files, this creates a real privacy and data-handling risk because sensitive images may be transmitted off-host without explicit user notice or consent.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/search.py (reported line 52)May include surrounding context.

python
"source_engine": engine_name,
        }
        for attr in ("similarity", "size", "source", "content"):
            val = getattr(item, attr, None)
            if val:
                r[attr] = str(val)[:300]
        if r["url"]:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The code writes full HTML responses from external services to predictable files under /tmp for diagnostics. Those responses may contain search metadata, URLs, query-derived content, anti-bot pages, or other sensitive data, and storing them on shared disk without disclosure or access controls increases the risk of local information exposure.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.