Back to skill

Security audit

YouTube Archiver

Security checks for vulnerabilities and agentic risk

Overview

This YouTube archiver is mostly coherent, but it needs review because it handles browser-authenticated YouTube data, API keys, external AI calls, and has under-scoped endpoint and file-write behavior.

Install only if you are comfortable giving the skill access to YouTube session cookies or a cookies.txt file and sending transcripts to the configured AI provider. Prefer dry runs, review .config.json carefully, avoid custom base_url values unless you fully trust the endpoint, use least-privilege API keys, and keep playlist names as simple folder labels without path characters.

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

Error
Location
scripts/yt_utils.py:1126
Finding
Unrestricted LLM Endpoint Can Exfiltrate API Credentials and Transcript Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/yt_utils.py`, lines 1126-1144 **Vulnerability Type**: Unrestricted credential-bearing network destination and server-side request forgery **Risk Level**: High ### Vulnerable Code ```python def _call_openai_compatible(prompt, provider_cfg, timeout_s, temperature, max_tokens, default_url, auth_header=True): model = str(provider_cfg.get("model", "")).strip() base_url = str(provider_cfg.get("base_url", "")).strip() or default_url api_key_env = str(provider_cfg.get("api_key_env", "")).strip() api_key = os.environ.get(api_key_env, "") if api_key_env else "" headers = {"Content-Type": "application/json"} if auth_header: if not api_key: return None headers["Authorization"] = "Bearer {0}".format(api_key) payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": temperature, "max_tokens": max_tokens, } try: data = _http_post_json(base_url, payload, headers=headers, timeout_s=timeout_s) except Exception: return None ``` Equivalent unrestricted `base_url` behavior is also present for Anthropic at lines 1168-1188 and Gemini at lines 1203-1232. ### Technical Analysis The provider configuration accepts an arbitrary `base_url`. The code reads an API credential from the environment, places it in an authorization header, and transmits it to that URL without: - Validating the destination hostname against the selected provider. - Requiring HTTPS for credential-bearing remote requests. - Rejecting loopback, link-local, or private-network destinations. - Requiring explicit confirmation before sending data to a custom endpoint. - Preventing an official-provider credential from being reused with an unrelated host. The request payload also contains the LLM prompt. During summary generation, this prompt includes complete transcript chunks. During tagging, it includes ...[truncated 2083 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist the official origin and path for each cloud provider: - OpenAI: `https://api.openai.com` - Anthropic: `https://api.anthropic.com` - Gemini: `https://generativelanguage.googleapis.com` - OpenRouter: `https://openrouter.ai` 2. Reject custom destinations by default. Require an explicit option such as `allow_custom_endpoint: true`. 3. Require HTTPS for every remote endpoint that receives credentials. Permit plain HTTP only for verified loopback Ollama addresses such as `127.0.0.1`, `localhost`, or `::1`. 4. Do not send official-provider credentials to custom origins. Use a separate custom-endpoint credential setting and environment variable. 5. Parse URLs with `urllib.parse.urlsplit` and reject embedded credentials, unexpected schemes, malformed hosts, link-local addresses, and private-network destinations unless explicitly authorized. 6. Resolve and display the effective destination before transmitting transcript content, and obtain informed user consent for non-local providers. 7. Warn users that cloud summarization sends transcript content to the selected provider. 8. Avoid placing API keys in URL query strings. Gemini credentials should always be carried in an appropriate request header. 9. Consider disabling automatic redirects or revalidating every redirect destination before forwarding credentials. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/yt_utils.py:1690
Finding
Playlist Name Path Traversal Allows Writes Outside the Archive Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/yt_utils.py`, lines 1690-1691 and 1758-1760 **Vulnerability Type**: Path traversal and out-of-scope file write **Risk Level**: Medium ### Vulnerable Code ```python folder_name = configured_name or detected_name or pid folder = output_dir / folder_name for video in videos: video_id = str(video.get("id", "")).strip() if not video_id: stats["failed"] += 1 continue if video_id in existing_ids: stats["skipped"] += 1 continue title = str(video.get("title", "Untitled")) channel = str(video.get("channel", "Unknown")) url = "https://www.youtube.com/watch?v={0}".format(video_id) duration = str(video.get("duration", "")) view_count = int(video.get("view_count", 0) or 0) published = normalize_upload_date(video.get("published", "")) note_path = folder / note_filename(title, video_id) ``` The resulting path is later written without a containment check: ```python try: write_note(note_path, frontmatter, summary_text=summary_text, transcript_text=transcript_text) existing_ids[video_id] = note_path stats["created"] += 1 ``` The write helper creates parent directories and overwrites the target path if it already exists: ```python def write_note(path, frontmatter, summary_text="", transcript_text=""): path = Path(path) ensure_dir(path.parent) title = str(frontmatter.get("title", "Untitled")) channel = str(frontmatter.get("channel", "Unknown")) duration = str(frontmatter.get("duration", "")) url = str(frontmatter.get("url", "")) content = dump_frontmatter(frontmatter) content += "\n\n" content += _build_body(title, channel, duration, url, summary_text, transcript_text) path.write_text(content, encoding="utf-8") ``` ### Technical Analysis A playlist's configured `name` is treated as a filesystem path rather than a single safe directory component. Configuration validation only checks tha ...[truncated 2039 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat playlist names as directory labels rather than paths. 2. Reject absolute paths, `.` and `..` components, null bytes, and all platform-specific path separators. 3. Apply a dedicated directory-name sanitizer to both configured and remotely detected playlist names. 4. Resolve the candidate directory and verify containment before writing: ```python archive_root = output_dir.resolve() safe_name = sanitize_directory_name(folder_name) folder = (archive_root / safe_name).resolve() if folder != archive_root and archive_root not in folder.parents: raise ConfigError("Playlist folder escapes the output directory") ``` 5. Repeat the containment check on the final `note_path` immediately before writing to reduce future regression risk. 6. Use non-overwriting creation semantics where practical, or explicitly verify that an existing path belongs to the expected archived video before replacing it. 7. Add tests for Unix and Windows traversal forms, including `../x`, `..\x`, absolute paths, drive-qualified paths, UNC paths, and nested separators. 8. Validate playlist names during configuration loading so unsafe configuration fails before any network access or filesystem modification. ]]>
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
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (16)

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
use.
3. **macOS:** The terminal app (Terminal, iTerm, etc.) needs **Full Disk Access** in System Settings > Privacy & Security to read Chrome's cookie database. Without it, `yt-dlp` silently gets no cookies and private playlists appear empty.
4. **macOS (Chrome specifically):** Chrome may also prompt "allow Terminal to access data from other apps" — click Allow.
5. Test directly:
   - `yt-dlp --cookies-from-browser chrome --flat-playlist "https://www.youtube.com/playlist?list=LL"`
6. If browser cookie extraction still fails, export cookies via a browser extension (e.g. "Get cookies.txt LOCALLY") and set `cookies_file` in config.

---

## Rate limiting / transient network errors

### Symptoms
- HTTP 429
- Timeouts
- intermittent 5xx errors

### Fixes
1. Run in smaller batches: `yt-enrich.py --limit 5`.
2. Retry later; built-in backoff already retries transient failures.
3. Prefer authenticated requests (`browser`/`cookies_file`) over anonymous requests.

---

## Missing transcripts

#
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tainted flow: 'req' from pathlib.Path.read_text (line 1080, file read) → urllib.request.urlopen (network output)

High
Category
Data Flow
Content
for attempt in range(1, attempts + 1):
        req = urllib.request.Request(url, data=data, headers=headers)
        try:
            with urllib.request.urlopen(req, timeout=timeout_s) as response:
                raw = response.read().decode("utf-8", errors="replace")
                if not raw.strip():
                    return {}
Confidence
80% confidence
Finding
File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README explicitly states that the skill generates AI summaries and tags using external LLM providers, which implies transcripts and other video content may be transmitted off-device. Without a clear privacy warning and consent language, users may unknowingly send copyrighted, private, or sensitive viewing content to third-party services.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The activation phrase "Archive my YouTube playlists" is broad enough that an agent could initiate actions affecting multiple playlists, including private or sensitive ones, without clearly bounded scope or an explicit confirmation flow described in the README. In an agent setting, vague triggers increase the risk of over-collection and unintended access to personal data.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README mentions platform-specific cookie access behavior, including Full Disk Access on macOS and reading browser cookies for private playlists, but does not present this as a clear security warning. Access to browser cookies is highly sensitive because it can expose authenticated session material and expands the skill's effective privileges over the local system.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill documents capabilities that involve shell execution, network access, reading browser cookies, file writes, and use of environment-based API keys, but it declares no explicit tool scope or permission boundaries. That creates a real security issue because an agent may invoke the skill with broader-than-necessary privileges, increasing the blast radius for data exposure, cookie theft, arbitrary command execution, or unintended filesystem modification.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The invocation language is broad enough to match common user requests like importing, syncing, enriching notes, automation, or batch processing, which can cause the skill to trigger in situations where the user did not intend browser-cookie access, network retrieval, or local file writes. In this context, overbroad routing is risky because the skill performs sensitive actions against local files and authenticated YouTube data, so accidental invocation can lead to privacy and integrity issues.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs use of browser cookie extraction and API-key configuration without an explicit user-facing warning about the sensitivity of those credentials and the risks of local token exposure. This is dangerous because browser cookies for signed-in YouTube sessions can grant access to private playlists or account data, and poorly handled API keys can be leaked through logs, config files, or misconfigured environments.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This documentation instructs users to configure third-party AI providers and implies transcript summarization/tagging workflows, but it does not warn that video transcripts, metadata, or derived notes may be transmitted to external services. In a YouTube archiving skill, that omission can cause unintentional disclosure of private Watch Later/Liked content, transcript text, or other sensitive viewing data to remote providers.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The inline comment says `os.kill(pid, 0)` on Windows 'actually terminates the process', which contradicts what signal 0 is intended to do and what the code itself avoids by switching to `OpenProcess`. This is an active documentation contradiction about a process-management side effect, not merely an omission.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_command(cmd, timeout=120):
    try:
        completed = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The helper functions transmit prompts and transcript-derived content to configurable external AI endpoints, potentially alongside API credentials, without any built-in warning, confirmation, or endpoint restrictions. Because `base_url` is configurable, a user or downstream component could direct sensitive content to arbitrary servers, increasing privacy and exfiltration risk.

External Transmission

Medium
Category
Data Exfiltration
Content
def _call_anthropic(prompt, provider_cfg, timeout_s, temperature, max_tokens):
    model = str(provider_cfg.get("model", "")).strip()
    base_url = str(provider_cfg.get("base_url", "")).strip() or "https://api.anthropic.com/v1/messages"
    api_key_env = str(provider_cfg.get("api_key_env", "")).strip()
    api_key = os.environ.get(api_key_env, "") if api_key_env else ""
    if not api_key:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
timeout_s=timeout_s,
            temperature=temperature,
            max_tokens=max_tokens,
            default_url="https://api.openai.com/v1/chat/completions",
            auth_header=True,
        )
    elif provider == "openrouter":
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code sends transcript content to external LLM providers for summarization and tagging, which can expose sensitive or private video content to third-party services. In the context of an archiving skill that may process private playlists such as Watch Later or Liked Videos, users may not realize their viewing data and transcripts are being transmitted off-device.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This utility runs external commands via subprocess.run and is later used for yt-dlp and summarize invocations. Although subprocess execution is central to the implementation, this file itself provides no visible user disclosure such as a print/log message or inline warning about executing external binaries.

Static analysis

No suspicious patterns detected.