Back to skill

Security audit

emo-img — Give Your AI Emotional Expression

Security checks for vulnerabilities and agentic risk

Overview

This sticker-sending skill is coherent in purpose, but its helper script has unsafe file and network handling that could write or delete files outside its sticker folder and fetch untrusted URLs.

Review this before installing. Use it only if you are comfortable with a chat skill that can fetch online media, store files locally, and send media through messaging channels. Avoid using arbitrary download URLs or user-supplied sticker names until filename containment, URL allowlisting, TLS verification, size/type checks, and confirmation before external sends/removals are added.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sticker.py:38
Finding
HTTPS Requests Fall Back to Disabled Certificate Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, lines 38–42 **Vulnerability Type**: Improper TLS certificate validation **Risk Level**: High ### Vulnerable Code ```python # Last resort: skip verification ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE return ctx ``` ### Technical Analysis When the script cannot locate a usable CA certificate file, `_ssl_context()` silently creates an SSL context that disables both certificate verification and hostname validation. This context is subsequently used by the Tenor search and arbitrary URL download functions. Disabling `verify_mode` allows a server with an untrusted, expired, self-signed, or otherwise invalid certificate to be treated as legitimate. Disabling `check_hostname` also permits a certificate issued for an unrelated hostname. ### Attack Path 1. The script runs in an environment where none of the enumerated CA files is available and `certifi` is unavailable or unusable. 2. A user or agent invokes `search-online`, `search`, or `download`. 3. `_ssl_context()` reaches its fallback and returns a context with certificate checks disabled. 4. An attacker with a network interception position presents an arbitrary certificate. 5. The script accepts the certificate and exchanges data with the attacker-controlled endpoint. 6. The attacker can observe search terms and the Tenor API key or replace downloaded sticker data. ### Impact Assessment A network-positioned attacker can compromise the confidentiality and integrity of affected HTTPS traffic. The attacker may obtain search queries and the API key, manipulate Tenor responses, or substitute malicious and misleading content for downloaded files. This does not directly execute the downloaded content, but it compromises all security guarantees normally provided by TLS. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `CERT_NONE` fallback entirely. - Use `ssl.create_default_context()` with the operating system's trusted certificate store. - If a valid trust store cannot be initialized, fail closed and return a clear error. - Do not disable hostname validation under any circumstances. - Add automated tests confirming that self-signed, expired, and hostname-mismatched certificates are rejected. - Consider allowing a custom CA bundle only through an explicitly configured and trusted path. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sticker.py:142
Finding
Unrestricted URL Download Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, lines 142–161 **Vulnerability Type**: Server-side request forgery and unrestricted resource retrieval **Risk Level**: High ### Vulnerable Code ```python def download_and_add(url, name=None, tags=None): """Download a sticker from URL and add to local collection.""" ensure_dir() ext = ".gif" for e in [".gif", ".png", ".jpg", ".jpeg", ".webp"]: if e in url.lower(): ext = e break dest_name = name or "sticker" dest_file = STICKER_DIR / f"{dest_name}{ext}" counter = 1 while dest_file.exists(): dest_file = STICKER_DIR / f"{dest_name}_{counter}{ext}" counter += 1 try: req = urllib.request.Request(url, headers={"User-Agent": "OpenClaw-Sticker/1.0"}) with urllib.request.urlopen(req, timeout=15, context=_ssl_context()) as resp: dest_file.write_bytes(resp.read()) except Exception as e: print(f"Download failed: {e}", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis The `download` command accepts an arbitrary URL and passes it directly to `urllib.request.urlopen`. The code does not restrict URL schemes or hosts, resolve and reject private addresses, prevent access to loopback or link-local services, or validate redirect destinations. Depending on the URL handlers available in the Python environment, this may permit access to HTTP or HTTPS services on loopback, private networks, link-local metadata endpoints, or local resources. Any retrieved response is stored and indexed as though it were an image. ### Attack Path 1. An attacker persuades the user or agent to download a purported sticker from an attacker-selected URL. 2. The supplied URL targets an internal service, loopback interface, cloud metadata address, private network host, or supported local-resource scheme. 3. `urllib.request.urlopen` sends the request from the agent host, using its network posit ...[truncated 865 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Permit only the `https` scheme. - Restrict downloads to an explicit allowlist of trusted image hosts. - Reject URLs containing embedded credentials. - Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. - Revalidate the scheme, hostname, and resolved address after every redirect. - Defend against DNS rebinding by connecting only to the validated address or using a hardened HTTP client with SSRF controls. - Require an expected image MIME type and validate the decoded image format before indexing the file. - Do not provide a general-purpose arbitrary URL downloader through agent instructions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sticker.py:116
Finding
Unsanitized Sticker Names Permit Path Traversal and Arbitrary File Creation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, lines 116–124 and 151–158 **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```python dest_name = name or src.stem dest_file = STICKER_DIR / f"{dest_name}{src.suffix}" counter = 1 while dest_file.exists(): dest_file = STICKER_DIR / f"{dest_name}_{counter}{src.suffix}" counter += 1 shutil.copy2(str(src), str(dest_file)) ``` The same issue occurs in the download path: ```python dest_name = name or "sticker" dest_file = STICKER_DIR / f"{dest_name}{ext}" counter = 1 while dest_file.exists(): dest_file = STICKER_DIR / f"{dest_name}_{counter}{ext}" counter += 1 try: req = urllib.request.Request(url, headers={"User-Agent": "OpenClaw-Sticker/1.0"}) with urllib.request.urlopen(req, timeout=15, context=_ssl_context()) as resp: dest_file.write_bytes(resp.read()) ``` ### Technical Analysis The user-controlled `--name` value is directly joined to `STICKER_DIR`. No basename restriction, separator rejection, canonicalization, or containment validation is performed. A name containing `../` components can resolve outside the intended sticker directory. If the supplied name is absolute, `pathlib` discards the preceding `STICKER_DIR` component. Both `add_sticker` and `download_and_add` can therefore create files in unintended locations. Existing targets are not directly overwritten because the code adds a numeric suffix when a destination exists, but arbitrary new files can still be planted wherever the process has write permission. ### Attack Path 1. An attacker controls or influences the `--name` argument. 2. The attacker supplies a traversal name such as `../../some/destination` or an absolute path. 3. The script combines that name with `STICKER_DIR` without validating the resolved destination. 4. For `add`, attacker-selected local file content is copied to the escaped destination. 5. For `download`, attack ...[truncated 707 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict sticker names to a conservative basename pattern such as `[A-Za-z0-9._-]+`. - Reject empty names, absolute paths, path separators, `.` and `..` path components, control characters, and platform-specific alternate separators. - Generate server-controlled filenames instead of using user-provided names as filesystem paths. - Resolve the destination and verify it is strictly beneath the resolved `STICKER_DIR` before any copy or write. - Use `Path.relative_to()` or an equivalent containment check after canonicalization. - Avoid following symlinks in the destination path and use exclusive file creation to reduce race conditions. - Store only validated relative filenames in the index. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sticker.py:200
Finding
Untrusted Index Paths Permit Arbitrary File Deletion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, lines 200–211 **Vulnerability Type**: Path traversal leading to arbitrary file deletion **Risk Level**: High ### Vulnerable Code ```python def remove_sticker(name): """Remove a sticker from local collection by name.""" index = load_index() new_index = [] removed = False for entry in index: if entry["name"] == name: path = Path(entry["file"]) if path.exists(): path.unlink() removed = True print(f"Removed: {entry['name']}") else: new_index.append(entry) ``` ### Technical Analysis The deletion target is taken directly from the `file` field in `index.json`. The script does not verify that the path belongs to `STICKER_DIR`, is a regular sticker file, or is not a symlink. The path traversal issue in the add and download functions can also produce out-of-directory paths that are persisted in this index. Any party able to alter the index or create a malicious indexed entry can cause `remove_sticker` to call `unlink()` on an arbitrary path writable by the process. ### Attack Path 1. An attacker modifies `index.json` or uses the path traversal issue to create an indexed entry whose `file` value points outside `STICKER_DIR`. 2. The entry is assigned a known sticker name. 3. The attacker causes the `remove` command to be invoked for that name. 4. The script constructs `Path(entry["file"])` without containment validation. 5. If the target exists, `path.unlink()` deletes it. 6. The modified index is saved without the malicious entry, potentially obscuring the source of the deletion. ### Impact Assessment The process can delete any file for which its operating-system account has unlink permission. This can destroy user data, configuration files, application state, or availability-critical resources. System-wide files remain protected unless the process runs with elevated privileges, ...[truncated 65 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store only validated relative sticker filenames in `index.json`, not arbitrary or absolute paths. - Resolve the candidate deletion path and verify that it is strictly beneath the resolved `STICKER_DIR`. - Reject symlinks and require the target to be a regular file. - Validate the complete index schema when loading it, including required field types and safe filename constraints. - Refuse malformed or out-of-directory entries rather than attempting to process them. - Use directory-relative file operations with no-follow semantics where supported. - Apply restrictive permissions to `index.json` and the sticker directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/sticker.py:157
Finding
Unbounded Response Buffering Enables Memory and Disk Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, lines 157–158 **Vulnerability Type**: Uncontrolled resource consumption **Risk Level**: Medium ### Vulnerable Code ```python with urllib.request.urlopen(req, timeout=15, context=_ssl_context()) as resp: dest_file.write_bytes(resp.read()) ``` ### Technical Analysis Calling `resp.read()` without a maximum byte count buffers the complete response in memory. The resulting byte string is then written to disk without a file-size limit. The socket timeout does not impose a maximum response size and may not provide a strict total-operation deadline while data continues to arrive. The code also does not verify `Content-Length`, MIME type, file signature, or whether the response can be decoded as an image. ### Attack Path 1. An attacker supplies a URL controlled by the attacker. 2. The server returns a very large body or continuously streams data. 3. `resp.read()` attempts to accumulate the complete response in process memory. 4. Memory consumption grows until the operation ends or the process or host becomes unstable. 5. If buffering succeeds, `write_bytes()` writes the large response to storage, potentially exhausting disk capacity. ### Impact Assessment A remote content provider can cause denial of service affecting the sticker process and potentially the broader agent runtime. Excessive memory use may terminate the process or trigger host-level memory pressure. Excessive disk use can disrupt other applications sharing the account or filesystem. Arbitrary non-image content can also be stored under an image-like extension. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Stream responses in fixed-size chunks instead of calling unbounded `read()`. - Enforce a strict maximum sticker size appropriate for supported channels. - Reject responses whose declared `Content-Length` exceeds that limit. - Stop reading and delete the partial file if the actual byte count exceeds the limit. - Apply both connection/read timeouts and a total download deadline. - Validate the response MIME type, magic bytes, and successful image decoding. - Write to a temporary file in the sticker directory and atomically rename it only after all checks succeed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/sticker.py:86
Finding
Hardcoded Tenor API Key Exposes a Reusable Service Credential<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sticker.py`, line 86 **Vulnerability Type**: Hardcoded secret **Risk Level**: Medium ### Vulnerable Code ```python api_key = os.environ.get("TENOR_API_KEY", "AIzaSyAyimkuYQYF_FXVALexPuGQctUWRURdCYQ") # Tenor demo key ``` ### Technical Analysis A reusable Tenor API key is embedded directly in the distributed source code and automatically used whenever `TENOR_API_KEY` is not configured. Labeling it as a demo key does not prevent extraction or unauthorized reuse. Source-distributed credentials cannot be kept confidential. Any person with access to the package, repository, logs, or audit output can recover the key and use it independently of the skill. ### Attack Path 1. An attacker obtains the publicly distributed skill source. 2. The attacker extracts the hardcoded API key from line 86. 3. The attacker submits independent requests to the Tenor API using that key. 4. Automated abuse consumes the key's quota or triggers provider-side abuse controls. 5. Legitimate online sticker searches may become rate-limited or unavailable. ### Impact Assessment The primary impact is unauthorized consumption of the associated API quota and disruption of legitimate service. Depending on provider-side configuration, abuse may also produce account-level operational consequences. The key does not, based on the reviewed code alone, grant local host privileges. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the fallback credential from source and require `TENOR_API_KEY` to be supplied through secure runtime configuration. - Fail with a clear configuration error when the key is absent. - Rotate or revoke the exposed key. - Apply provider-side API restrictions, minimal quotas, and service-specific permissions. - Avoid placing credentials in documentation, command lines, source control, or generated logs. - Use a secrets manager or permission-restricted environment configuration where available. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (17)

Tainted flow: 'req' from os.environ.get (line 164, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
url = f"https://tenor.googleapis.com/v2/search?{params}"
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "OpenClaw-Sticker/1.0"})
        with urllib.request.urlopen(req, timeout=10, context=_ssl_context()) as resp:
            data = json.loads(resp.read())
        results = []
        for item in data.get("results", []):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 164, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
try:
        req = urllib.request.Request(url, headers={"User-Agent": "OpenClaw-Sticker/1.0"})
        with urllib.request.urlopen(req, timeout=15, context=_ssl_context()) as resp:
            dest_file.write_bytes(resp.read())
    except Exception as e:
        print(f"Download failed: {e}", file=sys.stderr)
Confidence
90% confidence
Finding
The download command fetches an arbitrary user-supplied URL and writes the response body directly to disk. In the context of a chat assistant skill, this creates an SSRF-style network primitive and untrusted file ingestion path, which becomes more dangerous because TLS verification may be disabled by the custom SSL fallback.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill description emphasizes sending stickers in chat, but the documented behavior also includes local file management, remote downloading, and metadata storage that are not clearly declared as sensitive capabilities. This mismatch can mislead reviewers and users about what the skill will actually do, increasing the chance that file-system changes or network retrieval happen without informed consent.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
If certificate discovery fails, the code explicitly disables hostname and certificate verification for all HTTPS requests. This permits man-in-the-middle attacks against sticker search and download traffic, allowing tampering with responses and substitution of malicious or unexpected content.

Missing User Warnings

High
Confidence
99% confidence
Finding
Online searches and downloads can silently proceed with TLS verification disabled, giving users no indication that transport security has been weakened. In a skill that retrieves remote media, this materially increases exposure to interception and content tampering.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README states that the skill 'falls back to Tenor's massive library' but does not warn that search terms, emotional descriptors, or other user-derived queries may be sent to an external online service. This creates a privacy and transparency issue because users may reasonably assume sticker lookup is local-only unless outbound data sharing is disclosed.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README advertises cross-channel operation on WhatsApp, Telegram, Discord, iMessage, and more, and describes the agent as sending stickers automatically, but it does not warn that content will be posted to third-party messaging platforms. In this context, lack of disclosure and approval guidance can lead to accidental external posting, privacy leakage, or reputational harm.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The README encourages users to invoke the skill through broad natural-language requests like 'send me a happy sticker' or 'react with a thumbs up gif' without clarifying approval boundaries, channel constraints, or confirmation requirements before sending. In a messaging skill that can post across external platforms, overly broad activation guidance increases the risk of unintended outbound actions from ambiguous conversational context.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises and instructs operations that imply network access, local file reads/writes, and environment-variable use, but it declares no explicit tool scope or permissions. That makes the effective capability boundary unclear and increases the risk of over-privileged execution or accidental access to local files and remote content without appropriate review.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger text is broad enough to match common conversational phrases like wanting to send a meme or sticker, which can cause the skill to activate in situations where the user did not intend file download or message transmission. In a skill that can fetch remote content and send media externally, ambiguous activation materially raises the risk of unintended actions.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill instructs downloading remote media and sending it through external chat channels but provides no explicit warning about the privacy, safety, or consent implications. Users may not realize that remote content will be fetched, stored locally, and then transmitted to third parties, which creates risks around malicious files, sensitive metadata, and unintended disclosure.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The workflow example starts from very general user language and proceeds directly into search and send actions without clear invocation constraints. Because the skill handles both local files and online content, this ambiguity can lead to automatic execution when the user may only be asking casually rather than authorizing retrieval and transmission.

Session Persistence

Medium
Category
Rogue Agent
Content
def _ssl_context():
    """Create an SSL context with robust cert discovery for macOS."""
    cert_paths = [
        os.environ.get("SSL_CERT_FILE", ""),
        "/private/etc/ssl/cert.pem",
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
except Exception:
                continue

    # Last resort: skip verification
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Tainted flow: 'dest_file' from os.environ.get (line 131, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Content
dest_file = STICKER_DIR / f"{dest_name}_{counter}{src.suffix}"
        counter += 1

    shutil.copy2(str(src), str(dest_file))

    index = load_index()
    entry = {
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The remove command unlinks the sticker file from disk and rewrites the index, which is an irreversible local data modification. The code prints a message after deletion, but there is no confirmation prompt, pre-action warning, or inline documentation warning users that files will be deleted.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The instruction to auto-detect the channel from conversation context encourages sending media to an external destination without explicit user opt-in at send time. In a messaging context, even a correct guess can result in accidental disclosure to the wrong platform or recipient, making automation risky.

Static analysis

No suspicious patterns detected.