Back to skill

Security audit

Bilibili & YouTube Watcher

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent video transcript helper, but it uses an external downloader with weak URL scoping and unpinned installation guidance that deserve review before installation.

Review this skill before installing. Use it only with video URLs you trust, avoid enabling browser-cookie access unless you intentionally need it, and prefer installing yt-dlp through a pinned, verified, least-privilege environment rather than the README's system-wide latest-download command.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/get_transcript.py:17
Finding

Bypassable Hostname Allowlist Permits Requests to Attacker-Controlled Hosts

Content
View full analysis

Vulnerability Details

File Location: scripts/get_transcript.py, lines 17-25; unvalidated URL execution occurs at lines 98-115
Vulnerability Type: Improper hostname validation
Risk Level: Medium

Vulnerable Code

python
def detect_platform(url: str) -> str:
    """Detect video platform from URL."""
    domain = urlparse(url).netloc.lower()
    
    if any(d in domain for d in ['youtube.com', 'youtu.be', 'youtube-nocookie.com']):
        return 'youtube'
    elif any(d in domain for d in ['bilibili.com', 'b23.tv']):
        return 'bilibili'
    else:
        return 'unknown'

The accepted URL is subsequently passed unchanged to yt-dlp:

python
        if platform == 'bilibili':
            cmd.extend([
                "--add-header", "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
                "--add-header", "Referer: https://www.bilibili.com/",
            ])
        
        cmd.append(url)
        
        try:
            result = subprocess.run(cmd, cwd=temp_dir, check=True, capture_output=True)

Technical Analysis

detect_platform() checks whether an allowed domain string occurs anywhere in urlparse(url).netloc. This is substring matching rather than validation against an exact hostname or a legitimate subdomain boundary.

Consequently, attacker-controlled hostnames such as youtube.com.attacker.example, fakebilibili.com, or b23.tv.attacker.example satisfy the check. The URL is then passed directly to yt-dlp, causing the dependency to process and potentially contact a host outside the advertised YouTube and Bilibili trust boundary.

Using an argument list with subprocess.run() prevents conventional shell metacharacter injection in this code path. The vulnerability is instead an outbound destination-validation weakness. Redirect handling performed by yt-dlp may also allow subsequent requests to destinations not ...[truncated 1193 chars]

Remediation
View remediation

Remediation Suggestions

Validate the parsed hostname using exact equality or a dot-delimited subdomain boundary:

python
from urllib.parse import urlparse

ALLOWED_HOSTS = {
    "youtube": (
        "youtube.com",
        "www.youtube.com",
        "m.youtube.com",
        "youtu.be",
        "youtube-nocookie.com",
        "www.youtube-nocookie.com",
    ),
    "bilibili": (
        "bilibili.com",
        "www.bilibili.com",
        "b23.tv",
    ),
}

def host_matches(host: str, allowed: str) -> bool:
    return host == allowed or host.endswith("." + allowed)

def detect_platform(url: str) -> str:
    parsed = urlparse(url)
    host = (parsed.hostname or "").lower().rstrip(".")

    if parsed.scheme != "https":
        return "unknown"

    for platform, allowed_hosts in ALLOWED_HOSTS.items():
        if any(host_matches(host, allowed) for allowed in allowed_hosts):
            return platform

    return "unknown"

Additional hardening should include:

  1. Reject URLs containing credentials in the authority component.
  2. Permit only required schemes, preferably HTTPS.
  3. Define whether arbitrary subdomains are necessary; use exact hostnames where possible.
  4. Review and restrict redirect behavior. If redirects must be followed, validate every redirect destination against the same allowlist.
  5. Add tests for malicious suffix and prefix cases, including youtube.com.example.org, notyoutube.com, and b23.tv.example.org.
  6. Consider applying network-level egress restrictions as defense in depth.

T08 · Insecure Dependencies

Note
Location
SKILL.md:12
Finding

Unpinned Executable Dependency Installation Weakens Supply-Chain Integrity

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, line 12; README.md, lines 19-28
Vulnerability Type: Unpinned third-party executable dependency without integrity verification
Risk Level: Low

Vulnerable Code

SKILL.md declares an unpinned package-manager dependency:

yaml
metadata: {"clawdbot":{"emoji":"📺","requires":{"bins":["yt-dlp"]},"install":[{"id":"brew","kind":"brew","formula":"yt-dlp","bins":["yt-dlp"],"label":"Install yt-dlp (brew)"},{"id":"pip","kind":"pip","package":"yt-dlp","bins":["yt-dlp"],"label":"Install yt-dlp (pip)"}]}}

README.md recommends unpinned package installation and downloading the mutable latest release:

bash
# macOS
brew install yt-dlp

# Linux
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod +x /usr/local/bin/yt-dlp

# Python
pip install yt-dlp

Technical Analysis

The installation procedures do not pin yt-dlp to a reviewed version. The direct-download procedure follows a mutable latest URL and does not verify a checksum or cryptographic signature before placing the file in /usr/local/bin and marking it executable.

The documented URL points to the official yt-dlp GitHub repository, and the project does not use a curl | sh pipeline. Nevertheless, the installed executable can change independently of the audited Skill. Package-manager resolution changes, an upstream account or release compromise, or a malicious future release could therefore alter the code executed by the Skill.

Attack Path

  1. A user follows the installation metadata or README instructions.
  2. The package manager resolves the current package version, or the direct download follows the mutable latest release URL.
  3. No project-controlled version constraint, checksum, or signature confirms that the retrieved artifact is the reviewed build.
  4. The retrieved artifact is installed as the yt-dlp executable.
  5. `scripts/get_transcript. ...[truncated 909 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin yt-dlp to a specific reviewed version in package metadata and installation instructions.
  2. Replace the mutable latest URL with a version-specific release URL.
  3. Publish the expected SHA-256 digest and verify it before installation:
bash
YT_DLP_VERSION="REVIEWED_VERSION"
EXPECTED_SHA256="REVIEWED_SHA256"

curl --fail --location \
  "https://github.com/yt-dlp/yt-dlp/releases/download/${YT_DLP_VERSION}/yt-dlp" \
  --output yt-dlp

printf '%s  %s\n' "$EXPECTED_SHA256" "yt-dlp" | sha256sum --check -
sudo install -m 0755 yt-dlp /usr/local/bin/yt-dlp
  1. Where supported, verify upstream release signatures or attestations in addition to checksums.
  2. Record dependency versions in a lock file or equivalent reproducible dependency manifest.
  3. Establish a controlled update process in which new versions are reviewed, tested, and assigned updated hashes before deployment.
  4. Avoid system-wide installation when it is unnecessary; prefer an isolated virtual environment or project-specific executable directory.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (9)

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

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

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

ja, es, fr | | Bilibili | zh-CN | en, zh-TW, ja |

Output Format

markdown
# Platform: Bilibili
# Language: zh-CN
# URL: https://www.bilibili.com/video/...

[Clean transcript text without timestamps...]

Troubleshooting

"yt-dlp not found"

Install yt-dlp first (see Installation section).

"HTTP Error 412" (Bilibili)

Your IP may be rate-limited. Solutions:

  1. Use cookies: yt-dlp --cookies-from-browser chrome "URL"
  2. Use proxy: export HTTP_PROXY="http://proxy:port"
  3. Wait and retry

"No subtitles found"

The video may not have subtitles available. Try:

  • Check if the video has CC (closed captions)
  • Try different language: --lang en or --lang zh-CN

License

MIT

Credits

Adapted from youtube-watcher by Michael Gathara.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
brew install yt-dlp

# Linux
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod +x /usr/local/bin/yt-dlp

# Python

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
brew install yt-dlp

# Linux
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod +x /usr/local/bin/yt-dlp

# Python

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill invokes a local Python script that reads files and shells out to yt-dlp, which implies shell and network-capable behavior, but the manifest does not declare any tool scope such as permissions or allowed-tools. That mismatch weakens reviewability and least-privilege controls, making it easier for a skill with executable/network behavior to run with broader-than-expected access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The trigger set includes generic phrases such as watch video, summarize video, and analyze video, which are broad enough to match many ordinary user requests. In a skill that can launch scripts and perform network access, overbroad activation increases the chance of unintended invocation and unnecessary external fetching.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script automatically sets transcript language to 'zh-CN' for Bilibili and 'en' for YouTube when the user does not provide a language. This imposes a locale choice based solely on platform rather than offering a neutral default or explicit user opt-in, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill extends beyond passive transcript reading by invoking an external downloader and adding spoofed headers specifically to bypass platform anti-scraping controls. In an agent setting, this increases legal/compliance risk and broadens the attack surface because untrusted input causes outbound requests via a powerful third-party tool with site-specific evasion behavior.

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 112)May include surrounding context.

python
cmd.append(url)
        
        try:
            result = subprocess.run(cmd, cwd=temp_dir, check=True, capture_output=True)
        except subprocess.CalledProcessError as e:
            error_msg = e.stderr.decode()
            if "unavailable" in error_msg.lower() or "not available" in error_msg.lower():

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The documentation states that the skill uses 'zh-CN for Bilibili, en for YouTube' as smart defaults, which imposes a language choice based on platform rather than explicit user preference. This is a natural-language locale policy concern because it preselects language behavior without an opt-in at the point of use.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.