Back to skill

Security audit

YouTube AI Videos

Security checks for vulnerabilities and agentic risk

Overview

This looks like a legitimate YouTube video-fetching skill, but one bundled helper can fetch user-supplied non-YouTube URLs and should be reviewed before installation.

Review before installing. The main video fetcher is straightforward, but avoid passing untrusted channel URLs to the bundled channel-ID helper until it validates HTTPS YouTube hostnames and rejects local/private addresses. Store the YouTube API key in an environment variable or the documented secrets file, and restrict the key to YouTube Data API v3.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/find_channel_id.py:17
Finding
Arbitrary URL Fetch Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/find_channel_id.py`, lines 17–24 and 28–30 **Vulnerability Type**: Server-Side Request Forgery (SSRF) caused by inadequate URL validation **Risk Level**: Medium ### Vulnerable Code ```python if channel_input.startswith('@'): url = f"https://www.youtube.com/{channel_input}" elif channel_input.startswith('UC'): return channel_input # Already a channel ID elif 'youtube.com/' in channel_input or 'youtu.be/' in channel_input: url = channel_input else: # Assume it's a channel name url = f"https://www.youtube.com/{channel_input}" try: # Fetch channel page req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) response = urlopen(req, timeout=10) html = response.read().decode('utf-8') ``` ### Technical Analysis The utility accepts an input as a YouTube URL whenever the raw string contains `youtube.com/` or `youtu.be/`. A substring match does not establish that the destination hostname belongs to YouTube. For example, `http://127.0.0.1:8080/youtube.com/` satisfies the substring check even though its destination is the local host. The complete attacker-controlled value is then passed to `urlopen()`. The implementation does not enforce HTTPS, validate the parsed hostname or port, reject local and private addresses, or constrain redirects. Python's URL opener follows HTTP redirects by default, so even an initially permitted destination could redirect the request elsewhere. This outbound-fetch capability exceeds the utility's minimum requirement, which only requires requests to known YouTube hosts. ### Attack Path 1. An attacker supplies a crafted argument to `find_channel_id.py`, directly or through an agent workflow that forwards user-provided channel input. 2. The argument contains a permitted substring but names an unintended destination, for example: ```text http://127.0.0.1:8080/youtube.com/ ``` 3. The condition at lines 21–22 accepts the entire string as a va ...[truncated 1156 chars]
Remediation
## Remediation Suggestions 1. Prefer accepting only YouTube handles and syntactically valid channel IDs. Avoid accepting arbitrary URLs unless necessary. 2. Parse URLs with `urllib.parse.urlparse()` and require: - Scheme exactly equal to `https` - No embedded username or password - No unexpected port - Hostname exactly equal to an explicit allowlisted hostname, such as `www.youtube.com`, `youtube.com`, `m.youtube.com`, or `youtu.be` 3. Do not use suffix-only or substring-based hostname checks. A value such as `youtube.com.attacker.example` must not be accepted. 4. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses using Python's `ipaddress` module. 5. Disable automatic redirects or validate every redirect target against the same scheme, hostname, port, and resolved-address policy. 6. Apply a response-size limit before reading the body to reduce memory-exhaustion risk. 7. Keep the existing timeout and consider separate connection and read limits where the HTTP client supports them. 8. Add regression tests for malicious inputs, including: ```text http://127.0.0.1/youtube.com/ http://169.254.169.254/youtube.com/ https://youtube.com.attacker.example/ https://attacker.example/youtube.com/ https://user@attacker.example/youtube.com/ ``` 9. For the main fetcher, continue restricting API calls to Google's documented HTTPS endpoint. Restrict the YouTube API key to YouTube Data API v3 and redact query-string credentials from diagnostics and intermediary logs.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
A description-behavior mismatch is a strong security signal because the skill claims to use the YouTube Data API and filter recent AI videos, while the detected behavior suggests unrelated channel resolution, direct HTML scraping, missing keyword/date filtering, and acceptance of command-line input. This kind of hidden or misleading behavior can conceal unauthorized collection, unexpected network destinations, or functionality outside the reviewed scope, making it easier to trick users and bypass policy review.

Session Persistence

Medium
Category
Rogue Agent
Content
This skill **requires** a YouTube Data API v3 key. Without it, the skill will not work.

1. Go to [Google Cloud Console](https://console.cloud.google.com)
2. Create a new project or use existing
3. Navigate to "APIs & Services" → "Library"
4. Search for "YouTube Data API v3" and enable it
5. Go to "APIs & Services" → "Credentials"
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
echo "YOUR_YOUTUBE_API_KEY" > ~/.openclaw/secrets/youtube_api_key.txt
chmod 600 ~/.openclaw/secrets/youtube_api_key.txt
```

#### Option B: Environment Variable
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill declares access to environment variables, local file reads, and network activity but does not define any explicit tool scope or permissions boundary. In an agent environment, this increases the attack surface because a user or reviewer cannot easily tell that the skill may read secrets and make outbound requests, which can enable unintended secret access or data exfiltration if the implementation is changed or abused.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The manifest describes a content-fetching/filtering skill, but this code also probes local user-specific secret storage under ~/.openclaw/secrets. Accessing local secret files is not an obvious requirement of the stated purpose, especially since the skill could rely on explicitly passed configuration or environment variables.

Tainted flow: 'req' from urllib.request.urlopen (line 86, network input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
try:
        req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        response = urlopen(req, timeout=10)
        data = json.loads(response.read().decode('utf-8'))
        
        if not data.get('items'):
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.

Tainted flow: 'req' from urllib.request.urlopen (line 86, network input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
try:
        req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        response = urlopen(req, timeout=10)
        data = json.loads(response.read().decode('utf-8'))
        
        if not data.get('items'):
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.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The README frames the skill around AI-focused YouTube channels generally, but the example output prominently uses a German video title and a curated set that appears partially German-language without stating that results may be language-specific or giving the user a language choice. This can amount to an implicit locale preference in the skill description rather than an explicit user-selected option.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The docstring for matches_keywords says it checks 'title or transcription', yet the function only lowercases and searches the title; the transcription parameter is unused. This is an active documentation/code mismatch about the skill's filtering behavior.

Static analysis

No suspicious patterns detected.