Back to skill

Security audit

auto-captions-skill

Security checks for vulnerabilities and agentic risk

Overview

ClawScan rates this skill suspicious with medium confidence because scanners report that transcription may use cloud services based on available credentials despite a stated local default, along with a possible unsafe file upload path; analysis was partial.

Consider the suspicious verdict and avoid installing if you need transcription to stay local. Before use, clarify whether cloud transcription can be selected explicitly and what audio or vocabulary data it sends; also review the reported curl fallback and the API-key-in-URL guidance. The scan is incomplete: although all listed components were inspected, several local references could not be resolved, so these results do not establish that the skill is safe.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (26)

Tainted flow: 'req' from os.environ.get (line 129, 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/transcribe.py (reported line 131)May include surrounding context.

python
def http(url, headers, data, file_path=None, fields=None):
    req = urllib.request.Request(url, data=data, method="POST", headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=900) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        die(f"transcription API error {e.code}: {e.read().decode(errors='replace')[:400]}")

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The local-default privacy claim conflicts with Step 2's documented preference for cloud providers whenever their keys exist, potentially sending sensitive audio off-device based on ambient credentials rather than an explicit engine choice. The instruction to ask first mitigates this risk but is not an enforced control; the additional OpenAI route reported by the finding cannot be independently verified because its implementation is not supplied.

Content

No source excerpt is available for this finding.

Context Leakage

High
Category
Data Exfiltration
Confidence
85% confidence
Finding

Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Content

Scanner excerpt · scripts/upload_parts.py (reported line 2)May include surrounding context.

python
#!/usr/bin/env python3
"""Upload one file to a WoopSocial media upload session (standard library only).

WoopSocial uploads work in three steps:
  1. The agent calls `media_uploads_create_session` with the projectId and the file size in bytes.

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, mcp, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% 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.

Content

Scanner excerpt · evals/evals.json (reported line 77)May include surrounding context.

json
{
   "id": 15,
   "prompt": "Caption these three clips and post them to TikTok, Reels and Shorts next week.",
   "expected": "Checks social_accounts_list during Step 1; if not connected, gives the tagged signup link and only this agent's connect path once, mentions the Instagram Business or Creator rule and TikTok privacy early, carries on captioning; suggests connecting or restarting at the review pause, never during a render; at Step 6 builds post.json without asking again for known answers."
  },
  {
   "id": 16,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · references/woopsocial.md (reported line 35)May include surrounding context.

md
|---|---|
| Claude (claude.ai, Desktop, Cowork) | Add WoopSocial from the [Claude directory](https://claude.ai/directory/woopsocial), click Connect and sign in |
| Cursor | Install from [cursor.directory](https://cursor.directory/plugins/woopsocial-social-media-scheduler) and sign in |
| Claude Code | `claude mcp add --transport http woopsocial "https://api.woopsocial.com/mcp"`, then restart with `claude --continue` (keeps the conversation), `/mcp`, select woopsocial, Authenticate |
| Codex | `codex mcp add woopsocial --url https://api.woopsocial.com/mcp`, then `codex mcp login woopsocial`, then start a new session (`codex resume --last` keeps this one) |
| OpenClaw | Install the [WoopSocial skill on ClawHub](https://clawhub.ai/woopsocial/skills/social-media-scheduler), or add the MCP URL to the agent's MCP config |
| Agents without sign-in support | Create an API key at [app.woopsocial.com/api-access](https://app.woopsocial.com/api-access) and use `https://api.woopsocial.com/mcp?api_k=

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · references/woopsocial.md (reported line 36)May include surrounding context.

md
(claude.ai, Desktop, Cowork) | Add WoopSocial from the [Claude directory](https://claude.ai/directory/woopsocial), click Connect and sign in |
| Cursor | Install from [cursor.directory](https://cursor.directory/plugins/woopsocial-social-media-scheduler) and sign in |
| Claude Code | `claude mcp add --transport http woopsocial "https://api.woopsocial.com/mcp"`, then restart with `claude --continue` (keeps the conversation), `/mcp`, select woopsocial, Authenticate |
| Codex | `codex mcp add woopsocial --url https://api.woopsocial.com/mcp`, then `codex mcp login woopsocial`, then start a new session (`codex resume --last` keeps this one) |
| OpenClaw | Install the [WoopSocial skill on ClawHub](https://clawhub.ai/woopsocial/skills/social-media-scheduler), or add the MCP URL to the agent's MCP config |
| Agents without sign-in support | Create an API key at [app.woopsocial.com/api-access](https://app.woopsocial.com/api-access) and use `https://api.woopsocial.com/mcp?api_key=%2A%2A%2A |

External Transmission

Medium
Category
Data Exfiltration
Confidence
97% confidence
Finding

The fallback authentication instructions embed an API key in the MCP URL query string. Such URLs can expose reusable credentials through configuration exports, diagnostics, request logs, or copied connection details, potentially enabling unauthorized access to connected publishing accounts.

Content

Scanner excerpt · references/woopsocial.md (reported line 38)May include surrounding context.

md
| Claude Code | `claude mcp add --transport http woopsocial "https://api.woopsocial.com/mcp"`, then restart with `claude --continue` (keeps the conversation), `/mcp`, select woopsocial, Authenticate |
| Codex | `codex mcp add woopsocial --url https://api.woopsocial.com/mcp`, then `codex mcp login woopsocial`, then start a new session (`codex resume --last` keeps this one) |
| OpenClaw | Install the [WoopSocial skill on ClawHub](https://clawhub.ai/woopsocial/skills/social-media-scheduler), or add the MCP URL to the agent's MCP config |
| Agents without sign-in support | Create an API key at [app.woopsocial.com/api-access](https://app.woopsocial.com/api-access) and use `https://api.woopsocial.com/mcp?api_key=%2A%2A%2A |

The API key is a password: never paste it into shared chats or commit it. Claude Code and Codex
load new tools only in a new session, and a restart stops a running script: never add the

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% 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.

Content

Scanner excerpt · references/woopsocial.md (reported line 59)May include surrounding context.

md
THREADS, BLUESKY) and give the user all the links in one message. When they say they're done, call
`social_accounts_list` once, put each `socialAccountId` in `post.json`, run `post_plan.py` again,
and go straight on to uploading and validation, so the plan is scheduled in the same sitting. If a
link has expired, make a new one. Never ask the user to paste IDs.

## 3. Upload each video once

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/aclib.py (reported line 257)May include surrounding context.

python
def need_ffmpeg():
    if not shutil.which("ffmpeg") or not shutil.which("ffprobe"):
        die("ffmpeg is not installed. Mac: brew install ffmpeg. Windows: winget install Gyan.FFmpeg "
            "(then open a new terminal). Linux: sudo apt install ffmpeg (or your distro's package).")


def run(cmd):

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/aclib.py (reported line 397)May include surrounding context.

python
if b in NEEDS_SHAPER and not has_raqm():
        die(f"Burned-in captions in '{lang}' need Pillow with text shaping (libraqm), which this Python "
            "doesn't have. Mac: brew install fribidi harfbuzz libraqm, then pip install --force-reinstall "
            "--no-binary pillow pillow. Linux: sudo apt install libraqm0 (or libfribidi0). Windows: use "
            "the SRT file the skill wrote and the platform's own captions, or caption in another language.")
    if b in ARABIC_SCRIPT and not has_raqm():
        try:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/check_setup.py (reported line 46)May include surrounding context.

python
ok = False
    else:
        line("missing", "ffmpeg", "Mac: brew install ffmpeg. Windows: winget install Gyan.FFmpeg (then open a "
             "new terminal). Linux: sudo apt install ffmpeg")
        ok = False
    routes = []
    try:

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/aclib.py (reported line 261)May include surrounding context.

python
def run(cmd):
    r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
    if r.returncode != 0:
        tail = "\n".join(r.stderr.strip().splitlines()[-6:])
        die(f"{os.path.basename(cmd[0])} failed:\n{tail}")

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/aclib.py (reported line 271)May include surrounding context.

python
@functools.lru_cache(maxsize=None)
def ffmpeg_has(kind, name):
    """Whether this ffmpeg build has an encoder or filter."""
    r = subprocess.run(["ffmpeg", "-hide_banner", f"-{kind}"], capture_output=True, text=True,
                       encoding="utf-8", errors="replace")
    return bool(re.search(rf"\s{re.escape(name)}\s", r.stdout))

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/aclib.py (reported line 305)May include surrounding context.

python
"""width, height (as displayed, after rotation), fps, duration, codecs, audio, colour, size."""
    if not os.path.exists(path):
        die(f"{path} not found")
    r = subprocess.run(["ffprobe", "-v", "error", "-show_streams", "-show_format", "-of", "json", path],
                       capture_output=True, text=True, encoding="utf-8", errors="replace")
    try:
        d = json.loads(r.stdout or "{}")

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/aclib.py (reported line 481)May include surrounding context.

python
urllib.request.urlretrieve(url, part)
        except Exception:
            if shutil.which("curl"):
                subprocess.run(["curl", "-sfL", url, "-o", part], check=False, timeout=120)
        if os.path.exists(part) and os.path.getsize(part) > 10000:
            from PIL import ImageFont
            ImageFont.truetype(part, 20)  # a whole, readable font file

Tainted flow: 'part' from os.environ.get (line 473, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · scripts/aclib.py (reported line 481)May include surrounding context.

python
urllib.request.urlretrieve(url, part)
        except Exception:
            if shutil.which("curl"):
                subprocess.run(["curl", "-sfL", url, "-o", part], check=False, timeout=120)
        if os.path.exists(part) and os.path.getsize(part) > 10000:
            from PIL import ImageFont
            ImageFont.truetype(part, 20)  # a whole, readable font file

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/check_setup.py (reported line 33)May include surrounding context.

python
line("missing", "Pillow", "pip install pillow")
        ok = False
    if shutil.which("ffmpeg") and shutil.which("ffprobe"):
        out = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True).stdout.split("\n")[0]
        line("ok", "ffmpeg", (out.split() + ["", "", "?"])[2])
        if aclib.ffmpeg_has("encoders", "libx264"):
            line("ok", "H.264 encoder", "libx264")

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/transcribe.py (reported line 43)May include surrounding context.

python
if length is not None:
        cmd += ["-t", f"{length:.3f}"]
    cmd += ["-vn", "-ac", "1", "-ar", "16000", "-c:a", "libmp3lame", "-b:a", bitrate, dst]
    r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
    if r.returncode != 0 or not os.path.exists(dst):
        die(f"could not read the audio of {src}; does it have a sound track?")

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/transcribe.py (reported line 53)May include surrounding context.

python
import numpy as np
    cmd = ["ffmpeg", "-v", "error", "-i", src, "-map", f"0:a:{probe(src)['audio_index']}", "-vn", "-ac", "1",
           "-ar", "16000", "-f", "f32le", "-"]
    r = subprocess.run(cmd, capture_output=True)
    if r.returncode != 0 or not r.stdout:
        die(f"could not read the audio of {src}; does it have a sound track?")
    return np.frombuffer(r.stdout, dtype=np.float32).copy()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

Although this invocation avoids shell injection, its multipart fallback passes job-derived field values through curl's -F syntax. A vocabulary-derived prompt such as @/path/to/secret causes curl to upload that local file rather than send literal prompt text, if the certificate-error fallback is reached; an attacker-controlled job can therefore cause unintended file disclosure to the transcription provider.

Content

Scanner excerpt · scripts/transcribe.py (reported line 147)May include surrounding context.

python
cmd += ["-F", f"{name}={value}"]
            else:
                cmd += ["--data-binary", f"@{file_path}"]
            r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
            try:
                return json.loads(r.stdout)
            except json.JSONDecodeError:

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/transcribe.py (reported line 170)May include surrounding context.

python
def via_whisper_api(src, engine, tmp, lang, vocab):
    url, env, model = {
        "groq": ("https://api.groq.com/openai/v1/audio/transcriptions", "GROQ_API_KEY", "whisper-large-v3-turbo"),
        "openai": ("https://api.openai.com/v1/audio/transcriptions", "OPENAI_API_KEY", "whisper-1"),
    }[engine]
    key = os.environ.get(env)

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/transcribe.py (reported line 171)May include surrounding context.

python
def via_whisper_api(src, engine, tmp, lang, vocab):
    url, env, model = {
        "groq": ("https://api.groq.com/openai/v1/audio/transcriptions", "GROQ_API_KEY", "whisper-large-v3-turbo"),
        "openai": ("https://api.openai.com/v1/audio/transcriptions", "OPENAI_API_KEY", "whisper-1"),
    }[engine]
    key = os.environ.get(env)
    if not key:

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/transcribe.py (reported line 227)May include surrounding context.

python
q.append(("language", lang) if lang else ("detect_language", "true"))
        for term in (vocab or [])[:50]:
            q.append(("keyterm", term))
        url = "https://api.deepgram.com/v1/listen" + urllib.parse.urlencode(q)
        with open(part, "rb") as fh:
            data = fh.read()
        r = http(url, {"Authorization": f"Token {key}", "Content-Type": "audio/mpeg"}, data, part)

Default engine selection prioritizes cloud uploads over the advertised local transcription default.

Medium
Category
Excessive Agency
Confidence
98% confidence
Finding

The default auto engine selects Groq or Deepgram whenever their keys exist in the environment, before considering local engines. This contradicts the advertised local default and can upload confidential speech and vocabulary without an explicit cloud choice; printing a notice immediately before uploading does not obtain consent.

Content

No source excerpt is available for this finding.