Back to skill

Security audit

YouTube Research Assistant

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent YouTube transcript assistant, but its implementation has under-scoped network access and local file-read weaknesses that users should review before installing.

Review before installing. This skill should only be used in a contained environment until it validates YouTube URLs and video IDs, removes --no-check-certificates, and narrows generic triggers. Avoid using it with sensitive local text files or private network access available to the OpenClaw process.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/get_transcript.py:143
Finding
Path Traversal in Transcript Loading Allows Local Text File Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/get_transcript.py`, lines 143–149 and 312–326 **Vulnerability Type**: Path traversal and unauthorized local file read **Risk Level**: High ### Vulnerable Code ```python def load_transcript(video_id: str) -> Optional[str]: path = DATA_DIR / f"{video_id}.txt" if not path.exists(): return None return path.read_text(encoding="utf-8") ``` The untrusted value is obtained from the `ask` command and passed directly to the vulnerable function: ```python def cmd_ask(args): video_id = args.video_id if video_id in ("ACTIVE_VIDEO", "-", ""): video_id = get_active_video() if not video_id: print("❌ No active video in session.") sys.exit(1) transcript = load_transcript(video_id) if not transcript: print("❌ Transcript not found.") sys.exit(1) chunks = retrieve_chunks(transcript, args.question) ``` ### Technical Analysis The `ask` command treats `video_id` as a filename component without validating that it is a legitimate 11-character YouTube video ID. The expression: ```python DATA_DIR / f"{video_id}.txt" ``` does not prevent absolute paths, directory separators, or `..` traversal components. The resulting path is also not resolved and checked to ensure that it remains inside `DATA_DIR`. An attacker who can cause the skill to invoke the `ask` command can therefore provide a value such as `../../../../tmp/confidential`. The application appends `.txt`, resolves the traversal through normal filesystem semantics, and reads the resulting file if it exists and is valid UTF-8. The file content is subsequently processed by `retrieve_chunks()` and printed. This limits each invocation to selected chunks but does not prevent disclosure; questions can be adjusted to retrieve different portions of the target file. ### Attack Path 1. The attacker identifies or predicts a readable text file on the host whose file ...[truncated 1234 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate every supplied video ID before using it as a filesystem component: ```python VIDEO_ID_RE = re.compile(r"^[A-Za-z0-9_-]{11}$") def validate_video_id(video_id: str) -> str: if not VIDEO_ID_RE.fullmatch(video_id): raise ValueError("Invalid video ID") return video_id ``` Apply validation to both direct `ask` arguments and values loaded from session state. Add defense-in-depth containment checks: ```python def load_transcript(video_id: str) -> Optional[str]: video_id = validate_video_id(video_id) data_root = DATA_DIR.resolve() path = (data_root / f"{video_id}.txt").resolve() if path.parent != data_root: raise ValueError("Transcript path escapes the data directory") if not path.is_file(): return None return path.read_text(encoding="utf-8") ``` Additional hardening measures: - Reject absolute paths, path separators, null bytes, and traversal components. - Validate video IDs before writing them to `session.json`. - Treat persisted session data as untrusted when loading it. - Run the skill under a dedicated low-privilege account. - Add tests covering `../`, absolute paths, nested paths, malformed IDs, and manipulated session data. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/get_transcript.py:92
Finding
Insufficient URL Validation Permits Outbound Requests to Non-YouTube Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/get_transcript.py`, lines 92–107 and 166–185 **Vulnerability Type**: Unrestricted outbound request caused by weak URL validation **Risk Level**: High ### Vulnerable Code ```python def extract_video_id(url: str) -> str: patterns = [ r"(?:v=|\/)([0-9A-Za-z_-]{11})", r"youtu\.be\/([0-9A-Za-z_-]{11})", r"embed\/([0-9A-Za-z_-]{11})" ] for pattern in patterns: match = re.search(pattern, url) if match: return match.group(1) print("❌ Invalid YouTube URL", file=sys.stderr) sys.exit(1) ``` The original, unvalidated URL is subsequently passed to `yt-dlp`: ```python cmd = [ "yt-dlp", "--extractor-args", "youtube:player_client=android", "--skip-download", "--write-subs", "--write-auto-subs", "--sub-langs", lang, "--sub-format", "vtt", "--convert-subs", "vtt", "--no-playlist", "--no-write-info-json", "--no-write-playlist-metafiles", "--force-ipv4", "--retries", "3", "--fragment-retries", "3", "--sleep-requests", "1", "--no-check-certificates", "--output", "subs", url ] ``` ### Technical Analysis The first regular expression accepts any string containing a slash followed by 11 characters from the permitted video-ID character set. It does not parse the URL or validate its scheme, hostname, port, credentials, or destination. For example, a non-YouTube HTTPS URL containing an 11-character path segment can satisfy the expression. After extracting a nominal ID, the application passes the original URL to `yt-dlp`. Because `yt-dlp` supports numerous sites and generic URL extraction, the request is not inherently restricted to YouTube. This behavior conflicts with the documented network boundary in `SKILL.md`, which states that the only outbound request category is a subtitle fetch to YouTube. ### Attack Path 1. The attacker supplies a non-YouTube URL containing an ...[truncated 1278 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Parse URLs structurally and enforce an explicit allowlist before invoking `yt-dlp`: ```python from urllib.parse import urlparse, parse_qs ALLOWED_HOSTS = { "youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be", } def validate_youtube_url(raw_url: str) -> str: parsed = urlparse(raw_url) if parsed.scheme != "https": raise ValueError("Only HTTPS YouTube URLs are allowed") host = (parsed.hostname or "").lower().rstrip(".") if host not in ALLOWED_HOSTS: raise ValueError("URL host is not an approved YouTube host") if parsed.username or parsed.password: raise ValueError("URL credentials are not allowed") if parsed.port not in (None, 443): raise ValueError("Non-standard ports are not allowed") return raw_url ``` Then extract the video ID according to the validated host and URL structure, and enforce: ```python re.fullmatch(r"[A-Za-z0-9_-]{11}", video_id) ``` Additional controls should include: - Reconstructing a canonical URL such as `https://www.youtube.com/watch?v=VIDEO_ID` instead of passing the original URL. - Blocking redirects to destinations outside the approved host set where operationally possible. - Applying outbound firewall rules that permit only approved YouTube endpoints. - Rejecting ambiguous, malformed, credential-bearing, or non-HTTPS URLs. - Adding tests for lookalike domains, subdomain suffix tricks, encoded paths, unusual ports, IPv4/IPv6 literals, and non-YouTube URLs containing 11-character path segments. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/get_transcript.py:181
Finding
TLS Certificate Verification Is Disabled for Subtitle Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/get_transcript.py`, lines 181–184 **Vulnerability Type**: Improper certificate validation **Risk Level**: Medium ### Vulnerable Code ```python "--sleep-requests", "1", "--no-check-certificates", "--output", "subs", url ``` ### Technical Analysis The `--no-check-certificates` option instructs `yt-dlp` not to validate TLS certificates. As a result, HTTPS connections no longer provide reliable server authentication. An attacker with a network interception position may present an invalid or attacker-controlled certificate without causing the request to fail. The attacker could then modify subtitle content before it reaches the skill. This is particularly significant because the skill treats fetched transcripts as authoritative input for subsequent AI answers. A modified transcript could introduce false content or adversarial instructions into the model context. ### Attack Path 1. A user asks the skill to fetch a transcript. 2. The skill invokes `yt-dlp` with certificate validation disabled. 3. A network-positioned attacker intercepts the HTTPS connection. 4. The attacker presents an untrusted certificate, which the client accepts because verification is disabled. 5. The attacker returns altered VTT subtitle content. 6. The skill cleans and stores the manipulated transcript locally. 7. The altered transcript is used to produce summaries and answers, potentially affecting all later interactions involving that video. ### Impact Assessment A successful interception can compromise: - **Integrity:** Subtitle text and timestamps can be modified. - **Confidentiality:** Requested URLs and returned transcript data may be observed. - **Authenticity:** The skill cannot reliably determine whether it is communicating with the intended server. - **AI output integrity:** Poisoned transcript content can cause false or manipulated answers. Exploitation requires an attacker capable of intercepting or redirecti ...[truncated 120 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove the certificate-bypass option: ```python "--sleep-requests", "1", "--output", "subs", url ``` Certificate failures should cause the operation to fail safely rather than silently weakening transport security. Additional hardening measures: - Maintain an up-to-date operating-system certificate trust store. - Avoid documenting certificate bypasses as troubleshooting steps. - Use outbound HTTPS inspection only when the required enterprise certificate authority is securely installed in the trust store. - Log certificate failures without exposing sensitive URL parameters. - Combine proper certificate validation with the explicit YouTube hostname allowlist described in the URL-validation finding. - Add an integration test confirming that an untrusted or self-signed certificate is rejected. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/get_transcript.py:34
Finding
Unpinned Third-Party Dependency Installation Guidance Reduces Supply-Chain Integrity<![CDATA[ ## Vulnerability Details **File Location**: `scripts/get_transcript.py`, lines 34–38 **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Low ### Vulnerable Code ```python def check_dependencies(): yt_dlp_path = shutil.which("yt-dlp") if not yt_dlp_path: print("❌ yt-dlp not installed. Run: pip install yt-dlp", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis When `yt-dlp` is unavailable, the script recommends installing it with: ```bash pip install yt-dlp ``` This command does not specify an audited version or package hash. Consequently, installation resolves to whichever release the configured package index serves at that time. Runtime behavior can therefore change independently of the reviewed skill. The project also does not include a dependency lock file or hash-verified installation instructions. This reduces build reproducibility and leaves users dependent on the current state of the package repository and upstream distribution channel. No evidence was found that the named package is intentionally malicious or typosquatted. The issue is the unsafe, unpinned installation practice rather than a confirmed compromise of `yt-dlp`. ### Attack Path 1. The host does not have `yt-dlp` installed. 2. The script displays `pip install yt-dlp`. 3. An administrator or user follows the recommendation. 4. `pip` downloads the currently resolved package release from the configured package source. 5. A compromised upstream release, package-index account, mirror, or dependency could execute installation or runtime code with the user's privileges. 6. The newly installed executable is subsequently invoked by the skill. ### Impact Assessment The direct impact is reduced dependency integrity and reproducibility. If the distribution channel or an upstream release were compromised, malicious dependency code could execute with the privileges of the user running installation or OpenClaw. This finding does not ...[truncated 160 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pin a reviewed version of `yt-dlp` in a dependency manifest, for example: ```text yt-dlp==<reviewed-version> ``` For stronger integrity, use hash verification: ```text yt-dlp==<reviewed-version> \ --hash=sha256:<verified-package-hash> ``` Install dependencies with a controlled command such as: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` Additional supply-chain controls should include: - Commit a lock file or hash-pinned requirements file. - Download packages only from an approved index over verified TLS. - Review and update the pinned version on a defined maintenance schedule. - Verify release signatures or published checksums when available. - Run dependency vulnerability scanning in CI. - Avoid automatically installing missing dependencies at runtime. - Change the error message to reference the project's audited installation procedure rather than suggesting an unconstrained package installation. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

Harmful Content Injection

Critical
Category
Prompt Injection
Content
# YouTube Watcher Skill

A personal AI research assistant for YouTube videos that extracts transcripts and provides structured summaries, Q&A, deep dives, and actionable insights.

## Overview
Confidence
95% confidence
Finding
This content may contain harmful instructions that could cause physical harm if followed. CRITICAL: Review carefully before use.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared purpose suggests simple transcript-based summarization, but the skill also maintains persistent local storage, session tracking, listing of prior transcripts, and deletion behavior. That mismatch can mislead users and platform policy checks about the actual data retention and operational scope, increasing the risk of unintended data exposure or over-privileged deployment.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
"--retries", "3",
            "--fragment-retries", "3",
            "--sleep-requests", "1",
            "--no-check-certificates",
            "--output", "subs",
            url
        ]
Confidence
97% confidence
Finding
Using yt-dlp with --no-check-certificates disables TLS certificate validation for remote connections. That makes subtitle/video metadata retrieval vulnerable to man-in-the-middle interception or tampering, allowing an attacker on the network path to spoof responses, alter downloaded subtitle content, or redirect requests in ways the tool would normally reject.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill invokes shell commands, performs network access via yt-dlp, and reads/writes local files, but it does not declare any explicit tool scope or permission boundaries. This creates unnecessary trust ambiguity for reviewers and execution environments, making it easier for a skill with broader-than-expected capabilities to be installed or run without informed consent.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The trigger phrase `summarize video` is broad and can match ordinary user requests that are unrelated to YouTube transcript handling. This can cause unintended activation of a skill that performs shell execution, file I/O, and network access, creating an avoidable expansion of attack surface and possible data handling without clear user intent.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The `/summary` command is too generic and may collide with unrelated summarization workflows across the platform. In this skill, accidental activation is more concerning because execution can read session files, call shell commands, and fetch external subtitle data, all based on an ambiguous trigger.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The `/deepdive` trigger is generic and may invoke this skill in contexts unrelated to YouTube. Because the skill maintains persistent session state and can perform network and shell operations, unintended activation can lead to unnecessary processing of prior video context or external requests under the wrong user intent.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The `/actionpoints` trigger lacks product- or domain-specific scoping and can be accidentally matched by unrelated tasks. Given that the skill can access stored transcript history and session state, this ambiguity can expose prior context or trigger operations the user did not intend for a YouTube workflow.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill stores fetched transcripts plus session and index metadata under the user's home directory, creating persistent local data beyond the immediate summarization task. This expands the data exposure window: other local processes or users on the same system may be able to inspect viewing history, video URLs, and transcript contents, which may include sensitive or proprietary material.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
sys.exit(1)

    try:
        version = subprocess.run(
            ["yt-dlp", "--version"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill performs network retrieval and persists transcript data locally without any explicit user-facing notice about remote access or storage. In a transcript/summarization context this is relevant because users may assume ephemeral processing, while the implementation keeps transcript content and watch-history metadata on disk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:

            proc = subprocess.Popen(
                cmd,
                cwd=temp_dir,
                stdout=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
`fetch_subtitles` uses `lang: str = "en"`, which imposes an English-language default rather than asking the user to choose a language. The CLI also mirrors this default, so the skill prefers a specific locale without opt-in.

Context-Inappropriate Capability

Low
Confidence
80% confidence
Finding
Beyond transcript retrieval and question-oriented chunk extraction, the code provides commands to print session state and list all stored video transcript records. These are local data management capabilities that are not part of the manifest's stated user-facing purpose of fetching transcripts and producing structured outputs.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The `fetch` command defines `--lang` with `default="en"`, causing the skill to operate in English unless the user knows to override it. This is a language/locale policy concern because the tool selects a specific language by default rather than obtaining explicit preference.

Static analysis

No suspicious patterns detected.