Back to skill

Security audit

GIF Multi

Security checks for vulnerabilities and agentic risk

Overview

This GIF skill is mostly purpose-aligned, but it uses under-scoped message-sending and cleanup behavior that can affect local files or execute unsafe shell cleanup if channel names are attacker-influenced.

Install only if you are comfortable with GIF searches being sent to Giphy, the skill sending GIFs on its own in natural mode, and local cache cleanup behavior. Prefer setting mode to `on_request` and avoid using untrusted or arbitrary channel identifiers until cleanup is changed to a non-shell file operation and the cache is isolated to the skill.

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

Error
Location
SKILL.md:45
Finding

Shell Command Injection Through an Unsafely Generated Cleanup Path

Content
View full analysis
)` after sending. ``` ```python # Unique per-channel filename with timestamp — no topic/thread collisions safe_ch = channel.replace("/", "_").replace(" ", "_") ts = int(time.time() * 1000) out_base = os.path.join(out_dir, f"gif_{safe_ch}_{ts}") ``` ```python gif_path = os.path.join(out_dir, f"source_{channel}.gif") urllib.request.urlretrieve(result["gif_url"], gif_path) conv = convert(gif_path, channel, out_dir) ``` ### Technical Analysis The `--channel` value contributes directly to generated filenames. The only sanitization applied to the converted output filename replaces forward slashes and spaces: ```python safe_ch = channel.replace("/", "_").replace(" ", "_") ``` This does not remove or reject shell metacharacters such as semicolons, command substitutions, backticks, quotes, redirection operators, or newline characters. The source GIF filename uses the channel value without even this limited sanitization. The script returns the resulting path to the Agent, while `SKILL.md` instructs the Agent to delete that path using the shell-oriented operation `exec(rm )`. If the Agent interpolates the returned path into this command without robust argument separation or shell escaping, shell metacharacters embedded in the channel value can be interpreted as executable syntax. The use of `subprocess.run()` for FFmpeg does not itself introduce shell injection because it passes an argument list and does not enable `shell=True`. The vulnerable boundary is the documented cleanup operation performed after the script returns an attacker-influenced path. ### Attack Path 1. An attacker causes a crafted channel identifier to be passed to the script, directly th ...[truncated 1386 chars]
Remediation
View remediation
`. 4. Validate channel identifiers against a strict allowlist derived from `CHANNEL_PROFILES` or discovered channel IDs. 5. Generate cache filenames independently of user-controlled values, such as with `tempfile`, a UUID, or a cryptographically random token. 6. If a channel label must appear in a filename, permit only a narrow character set such as ASCII letters, digits, underscores, and hyphens. Reject rather than partially transform invalid identifiers. 7. Ensure the final resolved file path remains inside the expected cache directory before creating, returning, or deleting it. 8. Update `SKILL.md` to prescribe a non-shell cleanup mechanism. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gif_multi.py:88
Finding

Over-Broad Deletion of Unrelated Files in a Shared Cache Directory

Content
View full analysis
max_age: os.remove(fp) except OSError: pass ``` ```python # Cache in workspace (accessible by message tool) out_dir = os.path.join(os.path.dirname(SKILL_DIR), "..", ".gif_cache") out_dir = os.path.abspath(out_dir) os.makedirs(out_dir, exist_ok=True) _purge_old(out_dir) # Only old files (>10 min), leave recent ones alone ``` ### Technical Analysis The cache path is constructed by moving above the Skill directory and then appending `.gif_cache`. For the audited installation layout, this resolves to `/tmp/.gif_cache`, which is outside the Skill directory and can function as a shared location. The cleanup routine enumerates every regular file in that directory and deletes any file older than ten minutes. It does not verify that a file: - Was created by this Skill. - Has an expected GIF or MP4 filename. - Uses a Skill-specific prefix. - Is recorded in Skill-owned state. - Belongs to the expected user or process. - Resolves to a Skill-private cache location. Consequently, the cleanup behavior exceeds the minimum filesystem access necessary to convert and send GIFs. It can delete unrelated files placed in the same directory by other Skill instances, processes, or users. The broad `except OSError: pass` also suppresses cleanup errors, making unintended deletion behavior and race conditions harder to diagnose. ### Attack Path 1. Another proces ...[truncated 1222 chars]
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (23)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 209)May include surrounding context.

python
url = f"https://api.giphy.com/v1/gifs/search?api_key={api_key}&q={encoded}&limit=3&rating={rating}"

    req = urllib.request.Request(url, headers={"User-Agent": "openclaw-gif-multi/1.0"})
    with urllib.request.urlopen(req) as resp:
        data = json.loads(resp.read().decode())

    if not data.get("data"):

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The skill description presents a simple GIF reaction feature, but the documented behavior also performs channel discovery, external API use, local config persistence, cache management, and shell-based media processing. This mismatch can mislead users and reviewers about what data is sent externally and what local/system resources the skill can access.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

text
   - Or in `~/.openclaw/.env`:
     ```bash
     echo 'GIPHY_API_KEY=your_key' >> ~/.openclaw/.env
     ```

**3. Verify everything is ready**

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 56)May include surrounding context.

python
```
   - Or in `~/.openclaw/.env`:
     ```bash
     echo 'GIPHY_API_KEY=your_key' >> ~/.openclaw/.env
     ```

**3. Verify everything is ready**

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 105)May include surrounding context.

python
```
   - Or in `~/.openclaw/.env`:
     ```bash
     echo 'GIPHY_API_KEY=your_key' >> ~/.openclaw/.env
     ```

**3. Verify everything is ready**

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 106)May include surrounding context.

python
if api_key:
        return api_key
    # Fallback: read OpenClaw .env directly (when skills.entries.*.env is not set)
    env_path = os.path.expanduser("~/.openclaw/.env")
    if os.path.exists(env_path):
        with open(env_path) as f:
            for line in f:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill declares significant capabilities including shell, network, file read/write, and environment access, but does not scope or constrain them via explicit permissions. This increases the chance of over-privileged execution and makes review and enforcement difficult, especially because the workflow includes external API calls, config mutation, cache cleanup, and command execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill advertises auto-detection of channels and GIF search, but does not clearly warn users that their search terms will be transmitted to Giphy and that channel-related context may be used for routing behavior. This creates a privacy/transparency issue because users may not realize conversational content is being sent to a third party.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
## Initial setup

**1. Get a Giphy API Key**
   https://developers.giphy.com → "Create an App" → API (free, 1,000 req/day)

**2. Configure it**
   - Via `openclaw.json` (recommended):

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
84% confidence
Finding

The documented 'natural' mode authorizes the agent to decide on its own when to send GIFs, which introduces autonomous external actions and communication without a per-action request. In a messaging context, this can produce unintended outputs, spam, or disclosure of conversational cues to a third-party GIF provider.

Content

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

python3 {baseDir}/scripts/gif_multi.py --mode on_request

text

The user can also say it in conversation: "stop sending GIFs without asking" → switches to `on_request`. "feel free to send GIFs naturally" → switches to `natural`.

## Notes

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill allows mode changes based on broad conversational phrases like 'feel free to send GIFs naturally,' which can be triggered unintentionally or via prompt manipulation in normal conversation. That can silently switch the skill from explicit-consent behavior to autonomous behavior, causing unrequested external searches and message sends.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest and module documentation describe a sending capability across messaging platforms, implying message delivery or platform API interaction. In the implementation, the code discovers channel names, queries Giphy, downloads a GIF, converts it with ffmpeg, and prints JSON output, but contains no platform-send logic or calls to messaging APIs.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The inline documentation actively represents the action as sending content, which contradicts the implementation. The main flow ends by printing a JSON object with the converted file path, and no code performs a send operation to Telegram, WhatsApp, Discord, Signal, or other channels.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 48)May include surrounding context.

python
SETUP_HELP = """
━━━ Giphy API Key ━━━

1. Go to https://developers.giphy.com → "Create an App"
2. Select "API" (free, 1,000 requests/day)
3. Copy your API Key

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code persists configuration to disk and elsewhere in the file also deletes cached/source media files, but there is no user-facing disclosure at the point of execution beyond internal docstrings/comments. For a code file, file writes and deletions should have some visible warning, confirmation, or clear user disclosure unless already clearly covered; here the script silently modifies files under config and .gif_cache locations.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 132)May include surrounding context.

python
print(SETUP_HELP)

    # openclaw CLI
    r = subprocess.run(["openclaw", "--version"], capture_output=True, text=True, timeout=10)
    if r.returncode == 0:
        print(f"✅ OpenClaw: {r.stdout.strip()}")
    else:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 140)May include surrounding context.

python
# bins requeridos
    for bin_name in ("python3", "ffmpeg", "curl"):
        r = subprocess.run(["which", bin_name], capture_output=True, text=True, timeout=5)
        if r.returncode == 0:
            print(f"✅ {bin_name}")
        else:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 167)May include surrounding context.

python
def discover_channels():
    """Run `openclaw plugins list --json` and extract active channels."""
    result = subprocess.run(
        ["openclaw", "plugins", "list", "--json"],
        capture_output=True, text=True, timeout=15
    )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script sends the user's search query and the GIPHY API key in an outbound HTTP request to api.giphy.com, but the code provides no runtime disclosure that user input is being transmitted to a third-party service. Under the code-file criteria, network/HTTP calls that transmit user or system data should have some visible warning, logging, or documented disclosure unless the transmission is clearly disclosed elsewhere.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 206)May include surrounding context.

python
return {"error": "GIPHY_API_KEY not found", "help": SETUP_HELP}

    encoded = urllib.parse.quote(query)
    url = f"https://api.giphy.com/v1/gifs/search?api_key={api_key}&q={encoded}&limit=3&rating={rating}"

    req = urllib.request.Request(url, headers={"User-Agent": "openclaw-gif-multi/1.0"})
    with urllib.request.urlopen(req) as resp:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 247)May include surrounding context.

python
"-fs", str(profile["max_bytes"]),
            out_path
        ]
        subprocess.run(cmd, capture_output=True, text=True)
        _cleanup_source(gif_path)
        _purge_old(out_dir, max_age=600)
        return {"channel": channel, "format": "gif", "path": out_path}

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gif_multi.py (reported line 265)May include surrounding context.

python
"-fs", str(profile["max_bytes"]),
            out_path
        ]
        subprocess.run(cmd, capture_output=True, text=True)
        _cleanup_source(gif_path)
        _purge_old(out_dir, max_age=600)
        return {"channel": channel, "format": "gif", "path": out_path}

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The natural-language comment 'bins requeridos' is in Spanish while the rest of the skill is in English, introducing a language inconsistency without opt-in or documented locale justification. The policy requires avoiding forced language/locale choices unless users are given a choice or the constraint is justified.

Content

No source excerpt is available for this finding.