Back to skill

Security audit

Video Notes

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent video-notes tool, but it needs Review because it can access browser sessions, leave exported cookies in /tmp, install unpinned code at runtime, and generate scriptable HTML from untrusted captions.

Install only if you are comfortable running video-download tooling locally. Avoid using browser-cookie mode for private, paid, or sensitive accounts unless you understand the risk, and delete /tmp/yt-cookies-export.txt after any run. Prefer an isolated environment with pinned dependencies, and treat generated HTML from untrusted videos as potentially active content.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract_subtitles.py:103
Finding
Persistent Plaintext Export of Browser Session Cookies<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_subtitles.py`, lines 103–119 **Vulnerability Type**: Predictable and persistent storage of sensitive authentication data **Risk Level**: High ### Vulnerable Code ```python # First export cookies to a file so we don't re-authenticate per attempt if cookies_browser and not cookies_file: cookies_file = "/tmp/yt-cookies-export.txt" subprocess.run( [sys.executable, "-m", "yt_dlp", "--cookies-from-browser", cookies_browser, "--cookies", cookies_file, "--skip-download", "-o", "/tmp/yt_cookie_export_dummy", "--quiet", url], capture_output=True ) cookie_args = [] if cookies_file and os.path.exists(cookies_file): cookie_args = ["--cookies", cookies_file] elif cookies_browser: cookie_args = ["--cookies-from-browser", cookies_browser] ``` ### Technical Analysis When browser-based authentication is requested, the script exports browser cookies to the fixed path `/tmp/yt-cookies-export.txt`. The exported file is outside the lifetime of either `TemporaryDirectory` used elsewhere in the function, and the script never removes it. The code also does not explicitly create a private directory, enforce mode `0600`, verify ownership, or reject an existing symbolic link at that predictable path. The actual permissions may depend on `yt-dlp`, the operating system, and the process umask, but the Skill itself does not guarantee safe handling. Browser cookies are reusable authentication material. Depending on what `yt-dlp` exports and the services represented in the browser cookie store, the file may contain active YouTube, Google, Bilibili, or related session data. Persisting such credentials exceeds the minimum privilege needed to retrieve subtitles because the exported copy is only required during the fallback attempt. The subprocess return code is also ignored. A stale cookie file at the same path may consequently be reused if export f ...[truncated 1329 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid exporting browser cookies when direct `--cookies-from-browser` access is sufficient. 2. If an intermediate file is unavoidable, create it inside a private temporary directory: - Create the directory with mode `0700`. - Create the cookie file atomically with mode `0600`. - Use a cryptographically unpredictable filename. 3. Delete the cookie file in a `finally` block, including when `yt-dlp` fails or the process is interrupted. 4. Validate that the created file is a regular file owned by the current user before reuse. 5. Do not reuse a pre-existing fixed path or stale cookie file. 6. Check the export subprocess return code and fail closed if export is unsuccessful. 7. Document that browser-cookie access exposes authentication material and require explicit user consent. 8. Where possible, use a restricted cookie file containing only the minimum domains and accounts needed for the requested video. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/extract_subtitles.py:37
Finding
Automatic Installation of an Unpinned Runtime Dependency<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract_subtitles.py`, lines 37–44; duplicated in `scripts/capture_keyframes.py`, lines 129–136 **Vulnerability Type**: Unpinned dependency retrieval and execution at runtime **Risk Level**: Medium ### Vulnerable Code From `scripts/extract_subtitles.py`: ```python def ensure_yt_dlp(): try: import yt_dlp # noqa except ImportError: subprocess.check_call( [sys.executable, "-m", "pip", "install", "yt-dlp", "-q", "--break-system-packages"], stderr=subprocess.DEVNULL ) ``` The same behavior appears in `scripts/capture_keyframes.py`: ```python def ensure_yt_dlp(): try: import yt_dlp # noqa except ImportError: subprocess.check_call( [sys.executable, "-m", "pip", "install", "yt-dlp", "-q", "--break-system-packages"], stderr=subprocess.DEVNULL, ) ``` ### Technical Analysis If `yt_dlp` is unavailable, both scripts automatically invoke `pip` and install the mutable, unversioned package name `yt-dlp`. No reviewed version, artifact hash, lock file, or trusted index is enforced. Package installation can execute package build and installation logic with the privileges of the Skill process. The effective code retrieved in a future run can differ from the code that existed when the Skill was audited. Risk is increased by `--break-system-packages`, which intentionally bypasses protections used by externally managed Python environments and may modify the host Python installation. The command uses the process's normal pip configuration. A compromised package repository, malicious mirror, altered pip configuration, DNS or transport compromise outside pip's protections, or compromised future dependency release could therefore introduce attacker-controlled code. Automatic installation is not necessary for the declared note-generation workflow. The Skill can instead detect a missing dependency and inst ...[truncated 1220 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove runtime package installation from both scripts. 2. Declare `yt-dlp` as an explicit installation-time dependency. 3. Pin it to a reviewed version rather than resolving the latest release. 4. Use a lock file or requirements file with verified hashes, for example pip's `--require-hashes`. 5. Install dependencies in a dedicated virtual environment or isolated application environment. 6. Configure an approved package index explicitly rather than inheriting arbitrary user or host pip configuration. 7. Remove `--break-system-packages`. 8. If the dependency is absent, stop with a clear error that describes the secure installation procedure. 9. Add dependency-update review and vulnerability scanning to the release process. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
assets/note-template.html:152
Finding
Stored Script and HTML Injection Through Untrusted Subtitle Data<![CDATA[ ## Vulnerability Details **File Location**: `assets/note-template.html`, lines 152–185 **Vulnerability Type**: Unsafe JavaScript-context interpolation and DOM HTML injection **Risk Level**: High ### Vulnerable Code ```html <script> const S={{SUBTITLE_JSON}}; const KF={{KEYFRAMES_JSON}}; // Render keyframes gallery (function(){ const grid=document.getElementById('kfGrid'); if(!grid||!KF||!KF.length){ const sec=document.getElementById('keyframes'); if(sec) sec.style.display='none'; return; } const vid='{{VIDEO_ID}}'; grid.innerHTML=KF.map(k=>{ const u=`https://www.youtube.com/watch?v=${vid}&t=${Math.floor(k.s)}s`; const label=(k.text||'').slice(0,80)+((k.text||'').length>80?'…':''); return `<div style="background:var(--card);border:1px solid var(--border);border-radius:12px;overflow:hidden;cursor:pointer" onclick="window.open('${u}','_blank')"> <img src="data:image/jpeg;base64,${k.image_b64}" style="width:100%;display:block;aspect-ratio:16/9;object-fit:cover" loading="lazy"> <div style="padding:10px 12px"> <div style="font-family:monospace;font-size:11px;color:var(--accent);margin-bottom:4px">${k.t}</div> <div style="font-size:12px;color:var(--text2);line-height:1.5">${label}</div> </div> </div>`; }).join(''); })(); function rs(d,q=''){ const l=document.getElementById('sl'),c=document.getElementById('sc'); if(!d.length){l.innerHTML='<div style="padding:20px;text-align:center;color:var(--text3);font-size:13px">未找到匹配内容</div>';c.textContent='0 条';return;} const esc=s=>s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); l.innerHTML=d.map(s=>{ let t=s.text.replace(/</g,'&lt;'); if(q){const r=new RegExp(esc(q),'gi');t=t.replace(r,m=>`<mark>${m}</mark>`);} const vid='{{VIDEO_ID}}'; const u=`https://www.youtube.com/watch?v=${vid}&t=${Math.floor(s.s)}s`; return `<div class="si" onclick="window.open('${u}','_blank')"><span class="si-ts">${s.t}</span><span class="si-tx">$ ...[truncated 2940 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never place raw remote text directly into an executable script block. 2. Serialize data with an HTML-script-safe JSON encoder that escapes at least `<`, `>`, `&`, Unicode line separators, and script-closing sequences. Encoding `<` as `\u003c` prevents `</script>` termination. 3. Prefer storing JSON in a non-executable data element and parsing its `textContent`, while still applying safe HTML-context escaping. 4. Replace `innerHTML` construction with DOM APIs: - Create elements with `document.createElement`. - Assign remote text through `textContent`. - Register click behavior with `addEventListener`. - Set validated attributes through DOM properties. 5. Implement search highlighting by splitting text into text nodes and explicit `<mark>` elements rather than generating HTML strings. 6. Validate `VIDEO_ID` against the expected platform-specific identifier format. 7. Validate timestamps as finite, non-negative numbers before constructing URLs. 8. Validate Base64 image data and avoid allowing untrusted values to select arbitrary URL schemes. 9. Add a restrictive Content Security Policy. Remove inline event handlers and inline script where practical so the policy can omit `unsafe-inline`. 10. Add regression tests using captions containing `</script>`, HTML tags, quotes, event handlers, ampersands, and malformed Unicode. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill’s declared purpose is note generation, but the workflow also includes browser-cookie use and authenticated content access that are not transparently declared as sensitive capabilities. This mismatch is dangerous because users and policy systems may approve the skill for summarization while overlooking credential-adjacent behavior and shell-based media retrieval steps.

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

High
Category
YARA Match
Content
:不加 `--lang`
- YouTube / B站 中文视频:`--lang zh` 或 `--lang zh-Hans`

**YouTube 认证问题(常见!):**

若直接运行报错 `Sign in to confirm you're not a bot` 或 `No subtitles found`,需要传入浏览器 cookies:

```bash
# 使用 Chrome cookies(推荐)
python3 ~/.claude/skills/video-notes/scripts/extract_subtitles.py <url> \
  --output /tmp/subs.json \
  --cookies-from-browser chrome

# 或使用已导出的 cookies 文件
python3 ~/.claude/skills/video-notes/scripts/extract_subtitles.py <url> \
  --output /tmp/subs.json \
  --cookies /tmp/yt-cookies.txt
```

**脚本内置降级策略(自动执行,无需手动干预):**

1. **快速路径**:`--skip-download` 直接获取字幕(速度最快)
2. **降级路径**:若快速路径失败,自动导出 cookies 后用 `-f sb3`(storyboard 格式,YouTube 始终可用)触发字幕下载,解析 VTT 格式
3. **格式兼容**:优先 VTT(支持内联时间标签),失败则回
Confidence
95% confidence
Finding
The skill explicitly instructs use of `--cookies-from-browser chrome` and even states that cookies may be automatically exported as a fallback. Accessing browser cookies is credential-adjacent and can expose authenticated session tokens for YouTube/Bilibili accounts; if mishandled, those tokens could be abused to access private or paid content or leak user account state.

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

High
Category
YARA Match
Content
btitles.py <url> [--output <path>] [--lang <lang>]
                                       [--cookies-from-browser <browser>]
                                       [--cookies <cookie_file>]

Output JSON format:
    [{"t": "mm:ss", "s": <seconds_float>, "text": "<content>"}, ...]

YouTube 注意事项:
  - 若遇到 "Sign in to confirm" 或 "Requested format is not available",
    需要传入 --cookies-from-browser chrome(或 firefox/safari),
    脚本会自动先导出 cookies 文件,再以 storyboard 格式触发字幕下载。
  - 字幕格式优先尝试 VTT(与 yt-dlp storyboard 下载兼容),失败则回退 SRT。

哔哩哔哩注意事项:
  - 用 --lang zh 或 --lang zh-Hans 提取中文字幕。
  - 部分视频需要登录 cookie,用 --cookies-from-browser chrome。
  - 哔哩哔哩上传字幕(非 AI 生成)用 --write-subs 而非 --write-auto-subs,
    脚本已自动兼容处理。
"""

import sys
import re
import json
import subprocess
import temp
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README describes generating subtitles, screenshots, transcripts, and a self-contained local HTML document from video content, but it does not clearly warn users about the resulting data collection, local storage, and possible privacy/copyright implications. In a skill that processes potentially sensitive internal recordings or private/unlisted videos, this omission can lead users to expose or retain more content than they intended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill instructs the agent to read and write local files and execute shell commands, but it declares no explicit tool scope or permission boundaries. That creates an authorization gap: the runtime may permit powerful operations without transparent least-privilege constraints, increasing the risk of unintended file access or command execution beyond the user’s expectations.

Session Persistence

Medium
Category
Rogue Agent
Content
slug: video-notes
version: 1.1.0
author: 2992638402-art
description: 把 YouTube / 哔哩哔哩视频变成一份精美的结构化笔记。自动提取字幕、识别关键时刻并截图、生成核心论点总结和 SVG 图表,输出带侧边导航和全文搜索的单文件 HTML 文档。适用于技术演讲、公开课、播客、产品发布会等场景。This skill should be used when a user provides a YouTube or Bilibili URL and asks to take notes, summarize a video, or create a study document from video content.
---

# Video Notes Skill
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        import yt_dlp  # noqa
    except ImportError:
        subprocess.check_call(
            [sys.executable, "-m", "pip", "install", "yt-dlp", "-q", "--break-system-packages"],
            stderr=subprocess.DEVNULL,
        )
Confidence
98% confidence
Finding
Automatically installing yt-dlp at runtime via pip is dangerous because it executes package installation logic and modifies the host environment during normal skill execution. This creates a software supply chain risk, weakens reproducibility, and the use of --break-system-packages explicitly bypasses environment protections, increasing the chance of host compromise or destabilization if package resolution is tampered with or an unsafe mirror/index is used.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"-vf", "scale=960:-2",  # resize to 960px wide
        out_path,
    ]
    result = subprocess.run(cmd, capture_output=True)
    return result.returncode == 0 and os.path.exists(out_path)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Dynamically installing a package at runtime is beyond the minimum permissions and behavior expected from a note-taking/subtitle tool, especially when it modifies system packages. This creates supply-chain and environment-integrity risks, and the skill context makes it more concerning because users would not expect installation side effects from a content-processing helper.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        import yt_dlp  # noqa
    except ImportError:
        subprocess.check_call(
            [sys.executable, "-m", "pip", "install", "yt-dlp", "-q", "--break-system-packages"],
            stderr=subprocess.DEVNULL
        )
Confidence
92% confidence
Finding
The script installs yt-dlp dynamically at runtime via pip with --break-system-packages, which modifies the host environment and executes package installation code outside the stated subtitle-extraction task. This expands the trust boundary to package indexes/network delivery and can unexpectedly alter the system, making the skill more dangerous than a normal read/process utility.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
*extra_args,
            url,
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)
        for f in os.listdir(tmpdir):
            if f.endswith(f".{sub_fmt}") or f.endswith(".srt"):
                path = os.path.join(tmpdir, f)
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
*extra_args,
            url,
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)
        for f in os.listdir(tmpdir):
            if f.endswith(f".{sub_fmt}") or f.endswith(".srt"):
                path = os.path.join(tmpdir, f)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Exporting browser cookies to a fixed file path without explicit user-facing warning or secure lifecycle handling risks disclosure of active authenticated sessions. Because this skill may be run on user machines and the cookies can grant access to YouTube/Bilibili or other browser-scoped services, the context makes credential exposure materially dangerous.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# First export cookies to a file so we don't re-authenticate per attempt
    if cookies_browser and not cookies_file:
        cookies_file = "/tmp/yt-cookies-export.txt"
        subprocess.run(
            [sys.executable, "-m", "yt_dlp",
             "--cookies-from-browser", cookies_browser,
             "--cookies", cookies_file,
Confidence
95% confidence
Finding
This code exports browser-derived authentication cookies to a fixed path under /tmp, which can expose sensitive session material to other local processes or users depending on system configuration, race conditions, or leftover files. In the context of a skill processing untrusted inputs, handling browser cookies in a predictable shared location increases credential leakage risk beyond what is necessary for subtitle extraction.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
README 全部以中文描述,并在示例触发语中仅提供中文用法,没有说明是否支持其他语言或给予用户语言选择。按自然语言策略,若技能对语言/locale 作出隐含强制要求而无用户选择,可能构成语言政策问题。

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The document root sets `lang="zh-CN"`, which hard-codes a specific language/locale for rendered output. Under the policy, forcing a locale without offering user choice or documenting a justified regional constraint is a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The module docstring presents key usage guidance for YouTube and Bilibili in Chinese only, which imposes a language choice on users without opt-in. This is a natural-language policy concern because the file otherwise uses English for usage text and does not indicate that Chinese-only guidance is intentional or region-specific.

Static analysis

No suspicious patterns detected.