Back to skill

Security audit

Reel to Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned overall, but it can automatically upload extracted audio to OpenAI when an API key is present and uses broad, weakly scoped local/network handling.

Review before installing. Only use this with videos you are allowed to download and transcribe, avoid sensitive/private audio unless you explicitly want it sent to OpenAI, unset OPENAI_API_KEY for local-only operation, and choose a private per-run output directory instead of the documented shared /tmp path.

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/download_transcribe.py:5
Finding

Unrestricted User-Controlled URL Retrieval

Content
View full analysis

Vulnerability Details

File Location: scripts/download_transcribe.py, lines 5-10
Vulnerability Type: Server-Side Request Forgery through unrestricted URL handling
Risk Level: Medium

Vulnerable code:

python
parser = argparse.ArgumentParser()
parser.add_argument('url')
parser.add_argument('--out', default='reel-download')
args = parser.parse_args()
out = pathlib.Path(args.out); out.mkdir(parents=True, exist_ok=True)
video_tpl = str(out / 'video.%(ext)s')
subprocess.run(['yt-dlp', '-f', 'bv*+ba/best', '--merge-output-format', 'mp4', '-o', video_tpl, args.url], check=True)

Technical Analysis

The positional url argument is accepted without validating its scheme, hostname, resolved IP address, redirect destination, or relationship to the social-video services described by the Skill. It is passed directly to the general-purpose yt-dlp downloader.

Using a subprocess argument array prevents shell metacharacters in the URL from becoming shell commands, but it does not prevent network-level abuse. An attacker can submit a URL targeting an internal service or an attacker-controlled endpoint that redirects to a private address. Whether a particular target can be downloaded depends on protocol support, server behavior, and yt-dlp extraction behavior.

Because downloaded data is subsequently passed to ffmpeg, attacker-selected content also reaches a complex media parser. This expands the exposure beyond the intended TikTok, Instagram, X, and short-form-video use case.

Attack Path

  1. An attacker provides a URL pointing to an internal HTTP service, loopback interface, cloud metadata endpoint, or an external server that redirects to such a destination.
  2. The Agent follows the documented workflow and invokes download_transcribe.py with that URL.
  3. The script passes the unvalidated value to yt-dlp.
  4. yt-dlp attempts to connect from the Agent host, potentially using network access u ...[truncated 870 chars]
Remediation
View remediation

Remediation Suggestions

  1. Accept only https URLs and reject URLs containing credentials or unsupported schemes.
  2. Apply an explicit hostname allowlist for intended services, such as approved TikTok, Instagram, and X domains.
  3. Resolve the hostname before retrieval and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges.
  4. Revalidate every redirect destination rather than validating only the original URL.
  5. Require explicit user confirmation before accessing a domain outside the intended provider list.
  6. Run the downloader and media parser in a sandbox with restricted outbound networking, filesystem access, CPU time, memory, and file-size limits.
  7. Keep yt-dlp and ffmpeg patched because attacker-controlled media is passed to both tools.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_transcribe.py:7
Finding

Predictable Shared Temporary Directory Permits Symlink-Based File Overwrites

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, line 23; scripts/download_transcribe.py, lines 7-8, 12-13, 30, and 35
Vulnerability Type: Unsafe temporary-file handling and predictable output paths
Risk Level: Medium

Documented invocation:

bash
python3 skills/reel-to-skill/scripts/download_transcribe.py "<url>" --out /tmp/reel-skill

Vulnerable code:

python
out = pathlib.Path(args.out); out.mkdir(parents=True, exist_ok=True)
video_tpl = str(out / 'video.%(ext)s')
python
audio = out / 'audio.wav'
subprocess.run(['ffmpeg', '-y', '-i', str(mp4), '-vn', '-ac', '1', '-ar', '16000', str(audio)], check=True)
python
(out / 'transcript.txt').write_text(data.get('text',''))
python
(out / 'result.json').write_text(json.dumps(result, indent=2))

Technical Analysis

The documented workflow uses the fixed shared directory /tmp/reel-skill. The script creates the selected directory with exist_ok=True, so it accepts a pre-existing directory without verifying its owner, permissions, or whether path components are symbolic links.

Output files use predictable names. ffmpeg is invoked with -y, which authorizes replacement of an existing audio destination. Python's Path.write_text() opens existing destination paths normally and follows symbolic links. The script does not use exclusive creation, no-follow semantics, ownership checks, or a private per-run directory.

On a multi-user system, a local attacker who can prepare the predictable directory and its contents before the victim runs the documented command may place symbolic links at writable output paths. When the Skill executes with the victim's permissions, writes can be redirected to another file writable by that victim. Reusing the same directory can also mix or overwrite artifacts from separate runs.

Attack Path

  1. A local attacker predicts that the victim will use the documented ...[truncated 1566 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create a unique private directory for every run with tempfile.mkdtemp() or tempfile.TemporaryDirectory().
  2. Ensure the directory is owned by the current user and has mode 0700.
  3. Change the documented example to omit the fixed /tmp/reel-skill path or use a securely generated path.
  4. Reject an existing output directory by default. If reuse is explicitly requested, verify ownership, permissions, and every path component.
  5. Create output files atomically with exclusive creation and no-follow semantics, such as os.open() with O_CREAT | O_EXCL | O_NOFOLLOW where supported.
  6. Write results to securely created temporary files and atomically rename them into place after successful completion.
  7. Avoid unconditional overwrite behavior such as ffmpeg -y for destinations that were not securely created by the current run.
  8. Apply restrictive permissions to video, audio, transcript, and metadata files because they may contain private user content.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

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

python
part('file', audio.read_bytes(), audio.name, 'audio/wav')
    body.append(f'--{boundary}--\r\n'.encode())
    req = urllib.request.Request('https://api.openai.com/v1/audio/transcriptions', data=b''.join(body), headers={'Authorization': f'Bearer {key}', 'Content-Type': f'multipart/form-data; boundary={boundary}'})
    with urllib.request.urlopen(req, timeout=180) as r:
        data = json.loads(r.read().decode())
    (out / 'transcript.txt').write_text(data.get('text',''))
    result['transcript'] = str(out / 'transcript.txt')

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented purpose says the skill will analyze a workflow and generate an OpenClaw skill, but the described implementation only downloads/transcribes and may silently call the OpenAI transcription API if an API key is present. This mismatch is dangerous because users may trust the declared behavior while the actual behavior performs undeclared external data transfer and fails to implement key promised steps, undermining consent, predictability, and safe review.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
5. Keep `SKILL.md` concise and procedural.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs use of a script that downloads remote content, writes local files, may access environment variables, and invokes shell-capable tooling, but it declares no tool or permission scope. That creates an authorization and review gap: an agent or user may invoke broader capabilities than expected without explicit consent boundaries, increasing the chance of unintended network access, filesystem writes, or use of secrets from the environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The workflow directs downloading third-party videos locally and creating a new folder under skills//, but it does not warn the user about these side effects. In this context, that omission matters because the skill handles untrusted external URLs and performs persistent local writes, which can create storage, legal/compliance, and workspace integrity risks if the user did not knowingly authorize them.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill notes that OpenAI transcription may be used when OPENAI_API_KEY is available, but it does not provide a privacy warning that audio content will be sent to a third-party service. This is dangerous because users may supply private, copyrighted, or sensitive media and have its audio transmitted off-box without explicit notice or consent.

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

python
args = parser.parse_args()
out = pathlib.Path(args.out); out.mkdir(parents=True, exist_ok=True)
video_tpl = str(out / 'video.%(ext)s')
subprocess.run(['yt-dlp', '-f', 'bv*+ba/best', '--merge-output-format', 'mp4', '-o', video_tpl, args.url], check=True)
mp4 = next(out.glob('video.*'))
audio = out / 'audio.wav'
subprocess.run(['ffmpeg', '-y', '-i', str(mp4), '-vn', '-ac', '1', '-ar', '16000', str(audio)], check=True)

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

python
subprocess.run(['yt-dlp', '-f', 'bv*+ba/best', '--merge-output-format', 'mp4', '-o', video_tpl, args.url], check=True)
mp4 = next(out.glob('video.*'))
audio = out / 'audio.wav'
subprocess.run(['ffmpeg', '-y', '-i', str(mp4), '-vn', '-ac', '1', '-ar', '16000', str(audio)], check=True)
result = {'video': str(mp4), 'audio': str(audio)}
key = os.environ.get('OPENAI_API_KEY')
if key:

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This code performs external transmission of audio content to api.openai.com, which is security-relevant because it moves potentially sensitive user-derived data outside the local environment. In the context of a skill that downloads and analyzes arbitrary social video URLs, the likelihood of processing third-party content with privacy, copyright, or confidentiality implications makes undisclosed exfiltration more dangerous.

Content

Scanner excerpt · scripts/download_transcribe.py (reported line 30)May include surrounding context.

python
part('response_format', 'json')
    part('file', audio.read_bytes(), audio.name, 'audio/wav')
    body.append(f'--{boundary}--\r\n'.encode())
    req = urllib.request.Request('https://api.openai.com/v1/audio/transcriptions', data=b''.join(body), headers={'Authorization': f'Bearer {key}', 'Content-Type': f'multipart/form-data; boundary={boundary}'})
    with urllib.request.urlopen(req, timeout=180) as r:
        data = json.loads(r.read().decode())
    (out / 'transcript.txt').write_text(data.get('text',''))

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script uploads extracted audio from downloaded social media videos to an external transcription service whenever OPENAI_API_KEY is present, without any user-facing disclosure, consent, or opt-in. Because the skill is explicitly designed to process third-party social video content, this creates a real privacy and data-handling risk: users may unknowingly send copyrighted, personal, or sensitive speech off-host to a third party.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.