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

Content
View full analysis
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
View remediation
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

Content
View full analysis
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
View remediation
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

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
scripts/get_transcript.py:34
Finding

Unpinned Third-Party Dependency Installation Guidance Reduces Supply-Chain Integrity

Content
View full analysis
Remediation
View remediation
``` For stronger integrity, use hash verification: ```text yt-dlp== \ --hash=sha256: ``` 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
Confidence
95% confidence
Finding

This content may contain harmful instructions that could cause physical harm if followed. CRITICAL: Review carefully before use.

Content

Scanner excerpt · README.md (reported line 1)May include surrounding context.

md
# 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

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.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
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.

Content

Scanner excerpt · scripts/get_transcript.py (reported line 211)May include surrounding context.

python
"--retries", "3",
            "--fragment-retries", "3",
            "--sleep-requests", "1",
            "--no-check-certificates",
            "--output", "subs",
            url
        ]

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

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

python
sys.exit(1)

    try:
        version = subprocess.run(
            ["yt-dlp", "--version"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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

python
try:

            proc = subprocess.Popen(
                cmd,
                cwd=temp_dir,
                stdout=subprocess.PIPE,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.