Back to skill

Security audit

Social Downloader

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its transcription mode can automatically upload extracted audio to OpenAI when an API key is present without clearly telling the user or requiring a separate opt-in.

Review before installing. Use it only for URLs you are authorized to download, run it in a private output directory, and avoid running the transcription script with OPENAI_API_KEY in the environment unless you intend to send the video's audio to OpenAI and accept any privacy and billing implications.

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)

other

Warning
Location
scripts/download_transcribe.py:15
Finding
Automatic Disclosure of Extracted Audio to an External Transcription Service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_transcribe.py:15-31` **Vulnerability Type**: Privacy-sensitive external data transfer **Risk Level**: Medium ### Vulnerable Code ```python key = os.environ.get('OPENAI_API_KEY') if key: import mimetypes, uuid boundary = '----openclaw' + uuid.uuid4().hex body = [] def part(name, value, filename=None, ctype='text/plain'): body.append(f'--{boundary}\r\n'.encode()) if filename: body.append(f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\nContent-Type: {ctype}\r\n\r\n'.encode()); body.append(value); body.append(b'\r\n') else: body.append(f'Content-Disposition: form-data; name="{name}"\r\n\r\n{value}\r\n'.encode()) part('model', 'gpt-4o-mini-transcribe') 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()) ``` ### Technical Analysis When `OPENAI_API_KEY` is present in the process environment, the script automatically reads the complete extracted audio file into memory and sends it to the OpenAI transcription API. The behavior is triggered solely by the presence of the environment variable; there is no explicit command-line option, interactive confirmation, or per-execution consent requirement. Although the skill documentation requests transcription, it does not state that the media will be disclosed to an external service. This creates a privacy and data-governance risk when the downloaded media contains private conversations, confidential business information, personal data, or copyrighted material. Th ...[truncated 1261 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require an explicit option such as `--use-openai` before sending any content to the API. 2. Display or document the destination service, the type of data transmitted, and the applicable privacy implications. 3. Do not treat the mere presence of `OPENAI_API_KEY` as consent to upload data. 4. Consider requiring an additional confirmation flag such as `--confirm-external-upload` for non-interactive execution. 5. Provide an offline transcription mode where feasible. 6. Validate API responses and handle HTTP and JSON errors without exposing credentials or sensitive response data. 7. Update `SKILL.md` to clearly disclose that external transcription uploads the extracted audio and may incur API charges. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_transcribe.py:7
Finding
Predictable Output Paths Allow Local Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_transcribe.py:7-14` **Vulnerability Type**: Unsafe temporary and output file handling **Risk Level**: Medium ### Vulnerable Code ```python 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) 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)} ``` Related fixed-name writes at `scripts/download_transcribe.py:32-36`: ```python (out / 'transcript.txt').write_text(data.get('text','')) result['transcript'] = str(out / 'transcript.txt') else: result['transcript'] = None (out / 'result.json').write_text(json.dumps(result, indent=2)) print(json.dumps(result, indent=2)) ``` The documented invocation also recommends a predictable shared temporary path at `SKILL.md:20`: ```bash python3 skills/social-downloader/scripts/download_transcribe.py "<url>" --out /tmp/social-skill-source ``` ### Technical Analysis The script accepts or creates an output directory without verifying its ownership, permissions, or whether existing output entries are symbolic links. It then writes predictable filenames including `audio.wav`, `transcript.txt`, and `result.json`. The `ffmpeg` invocation uses `-y`, which enables unconditional overwrite of the destination. If `audio.wav` is an attacker-created symbolic link, the destination followed by `ffmpeg` may be overwritten with the privileges of the account running the skill. Python's `Path.write_text()` normally follows symbolic links as well, exposing `transcript.txt` and `result.json` to the same class of attack. The documented use of `/tmp/social-s ...[truncated 2402 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a fresh, private output directory with `tempfile.mkdtemp()` or `tempfile.TemporaryDirectory()` and restrictive permissions such as `0700`. 2. Avoid predictable shared paths under `/tmp`; if persistent output is required, create a randomized child directory beneath the requested destination. 3. Verify that an existing output directory is owned by the current user and is not writable by other users. 4. Before writing, use `os.lstat()` to reject symbolic links and unexpected file types. 5. Create output files atomically and exclusively using `os.open()` with `O_CREAT | O_EXCL | O_NOFOLLOW` where supported. 6. Write transcript and result data to securely created temporary files, then atomically rename them into place after validating the destination. 7. Avoid unconditional `ffmpeg -y` writes to pre-existing paths. Generate a unique destination filename for every invocation. 8. Capture the exact output path produced by `yt-dlp`, rather than selecting an arbitrary entry with `next(out.glob('video.*'))`. 9. Reject pre-existing `video.*`, `audio.wav`, `transcript.txt`, and `result.json` entries unless an explicit, safely implemented overwrite option is provided. 10. Update `SKILL.md` to recommend a freshly created private directory rather than the fixed `/tmp/social-skill-source` path. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
Findings (10)

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

Critical
Category
Data Flow
Content
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')
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes shell scripts and Python with network, filesystem, and likely environment access, but it declares no explicit tool scope or permissions boundary. In an agent system, this increases the chance the skill is auto-invoked with broader capabilities than intended, enabling unreviewed downloads, local file writes, and shell execution against user-supplied URLs.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description is broad enough to match many common requests to download, save, send, or transcribe social media content, which can cause the agent to trigger this skill in situations where the user did not clearly consent to local downloading or rights-sensitive processing. Overbroad triggers are dangerous because they expand the operational surface for shell/network actions and increase the chance of downloading untrusted or privacy-sensitive content automatically.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The activation guidance 'Use this for social video links' is ambiguous and does not define supported domains, content restrictions, or required consent steps. In context, that ambiguity matters because the skill performs network retrieval and local storage, so an imprecise trigger can lead to misuse on malicious URLs, non-social links, or content involving legal and privacy risk.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill tells the agent to download third-party media to local storage and possibly transcribe it, but it does not warn users that this creates local copies and may handle copyrighted, private, or otherwise sensitive material. This omission can undermine informed consent and lead to unsafe handling of personal data or policy-sensitive content, especially when combined with automated download and transcription workflows.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script invokes yt-dlp to fetch content from a user-supplied URL, which performs a network operation and writes media files into the output directory. There is no confirmation prompt, user-facing print/log message, or inline comment/docstring disclosing that the script will contact a remote service and save files locally.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

External Transmission

Medium
Category
Data Exfiltration
Content
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',''))
Confidence
90% confidence
Finding
This code performs external transmission of downloaded audio content to a third-party API. In isolation that can be legitimate, but within this skill the transmission is implicit and coupled to the mere presence of a credential, making it easy for users to trigger off-device data sharing without realizing it.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
If OPENAI_API_KEY is present, the script automatically uploads the extracted audio to an external transcription service without any explicit user notice, consent, or opt-in at runtime. In a social-video downloader context, downloaded media can contain personal, copyrighted, or sensitive voice content, so silent transmission materially increases privacy and compliance risk.

Static analysis

No suspicious patterns detected.