Back to skill

Security audit

video-downloader-enhanced

Security checks for vulnerabilities and agentic risk

Overview

This video downloader is mostly coherent, but it should be reviewed because ordinary fallback paths can automatically use Chrome login cookies, cloud transcription, and unpinned remote YouTube components.

Install only if you are comfortable with a downloader that may invoke yt-dlp against your live Chrome profile when public routes fail. Prefer --asr none or an explicit local ASR backend for sensitive videos, avoid relying on inherited SILICONFLOW_API_KEY in default auto mode, use trusted video URLs only, and consider an isolated browser profile if cookie-backed downloads are needed. No evidence of intentional theft, persistence, or destruction was found.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/asr.py:358
Finding
Default ASR selection can upload video audio to a cloud service without per-invocation consent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_video.py:32-39`, `scripts/download_video.py:100-108`, `scripts/asr.py:94-101`, `scripts/asr.py:358-378`, and `scripts/asr.py:492-496` **Vulnerability Type**: Implicit transmission of potentially sensitive media to a third-party service **Risk Level**: High ### Complete Code Snippet ```python # scripts/download_video.py:32-39 parser.add_argument( "--asr", choices=("auto", "whisper_cpp", "whisper", "siliconflow", "none"), default="auto", help=( "Audio transcription backend. Default: auto " "(whisper.cpp > openai-whisper > SiliconFlow)" ), ) ``` ```python # scripts/download_video.py:100-108 if not args.metadata_only and args.asr != "none" and result.get("video_path"): asr_result = run_asr( Path(result["video_path"]), Path(result["output_dir"]), backend=args.asr, model=args.asr_model, language=args.asr_language, prompt=args.asr_prompt, max_seconds=args.asr_max_seconds, ) ``` ```python # scripts/asr.py:94-101 if selected_backend == "siliconflow": transcribe_with_siliconflow( audio_path, transcript_path, siliconflow_json_path, model=resolved_model, ) raw_json_path = siliconflow_json_path ``` ```python # scripts/asr.py:358-378 api_key = os.environ.get("SILICONFLOW_API_KEY") if not api_key: raise RuntimeError("SILICONFLOW_API_KEY is required for ASR backend 'siliconflow'.") body, content_type = _multipart_body( fields={"model": model}, file_field="file", file_path=audio_path, file_content_type="audio/mpeg", ) request = Request( SILICONFLOW_TRANSCRIPTION_URL, data=body, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": content_type, }, method="POST", ) try: with urlopen(request, timeout=600) as response: response_text = response.read().decode("utf-8", errors="replace") ``` ...[truncated 2465 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make `auto` strictly local-only: - Check whisper.cpp and OpenAI Whisper. - If neither is available, return a clear error or pending result. - Do not select a network backend automatically. 2. Require explicit cloud authorization, such as: ```bash --asr siliconflow ``` or: ```bash --allow-cloud-asr ``` 3. Before uploading, display or return a structured disclosure stating: - The destination hostname. - The exact file being uploaded. - Whether the full audio or only a limited segment will be sent. - That a third party will process the content. 4. In Agent environments, require a per-task consent signal rather than inferring consent from the presence of an environment variable. 5. Consider adding a global offline mode that blocks every non-platform network request, including cloud ASR. 6. Retain the existing API-key redaction and extend it to unexpected exceptions and diagnostic output. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/providers/bilibili.py:72
Finding
Automatic fallback accesses local Chrome cookies without code-enforced consent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/providers/bilibili.py:72-80`, `scripts/providers/bilibili.py:89-105`, `scripts/providers/douyin.py:255-286`, `scripts/providers/youtube.py:64-71`, `scripts/providers/youtube.py:84-90`, and `scripts/providers/xiaohongshu.py:191-211`, `scripts/providers/xiaohongshu.py:452-475` **Vulnerability Type**: Automatic access to browser authentication material **Risk Level**: High ### Complete Code Snippet ```python # scripts/providers/bilibili.py:72-80 def _extract_metadata(url: str) -> dict: yt_dlp = _require_ytdlp() commands = [ [yt_dlp, "--no-playlist", "--dump-single-json", url], [yt_dlp, "--cookies-from-browser", "chrome", "--no-playlist", "--dump-single-json", url], ] return json.loads(_run_first_successful(commands)) ``` ```python # scripts/providers/youtube.py:64-71 def _extract_metadata(url: str) -> dict: commands = [ _base_ytdlp_command() + ["--dump-single-json", url], _base_ytdlp_command() + ["--remote-components", "ejs:github", "--dump-single-json", url], _base_ytdlp_command() + ["--cookies-from-browser", "chrome", "--dump-single-json", url], ] return json.loads(_run_first_successful(commands)) ``` ```python # scripts/providers/douyin.py:255-263 def _extract_metadata_with_ytdlp(url: str) -> dict: yt_dlp = _require_ytdlp() commands = [ [yt_dlp, "--no-playlist", "--dump-single-json", url], [yt_dlp, "--cookies-from-browser", "chrome", "--no-playlist", "--dump-single-json", url], ] return json.loads(_run_first_successful(commands)) ``` ```python # scripts/providers/xiaohongshu.py:191-211 # ---- Route 3: Chrome cookies ---- if _IS_REMOTE_ASSISTANT: # Don't block remote tasks — report immediately raise RuntimeError( "Anonymous yt-dlp and public direct URL both failed. " "Chrome Cookie auth is required but unavailable in remote mode. " "Please run this download f ...[truncated 3347 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all automatic `--cookies-from-browser` entries from default fallback lists. 2. Add an explicit option such as: ```bash --allow-browser-cookies ``` Keep it disabled by default and pass it to providers through an explicit typed option. 3. Require separate authorization for metadata and media operations. In particular, do not access browser cookies during `--metadata-only` unless the user specifically requests authenticated metadata. 4. Apply the remote/unattended restriction consistently to Douyin, Bilibili, YouTube, and Xiaohongshu. Prefer an explicit execution-context parameter over a module-level environment variable evaluated at import time. 5. Return a structured failure explaining that authenticated access is available but requires consent. The user or controlling Agent can then retry with the authorization flag. 6. Where practical, use a dedicated browser profile with minimal platform-specific credentials instead of the user's primary Chrome profile. 7. Continue to prohibit cookie export files and ensure subprocess output is filtered if future `yt-dlp` versions include sensitive diagnostics. ]]>

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/providers/youtube.py:64
Finding
YouTube fallback retrieves mutable executable components from GitHub at runtime<![CDATA[ ## Vulnerability Details **File Location**: `scripts/providers/youtube.py:64-69`, `scripts/providers/youtube.py:84-87`, and `scripts/providers/youtube.py:114-120` **Vulnerability Type**: Unpinned remote payload retrieval through yt-dlp **Risk Level**: Medium ### Complete Code Snippet ```python # scripts/providers/youtube.py:64-69 def _extract_metadata(url: str) -> dict: commands = [ _base_ytdlp_command() + ["--dump-single-json", url], _base_ytdlp_command() + ["--remote-components", "ejs:github", "--dump-single-json", url], _base_ytdlp_command() + ["--cookies-from-browser", "chrome", "--dump-single-json", url], ] ``` ```python # scripts/providers/youtube.py:84-87 commands = [ _download_command(output_path) + [url], _download_command(output_path, remote_components=True) + [url], _download_command(output_path, cookies=True) + [url], ] ``` ```python # scripts/providers/youtube.py:114-120 def _download_command( output_path: Path, *, remote_components: bool = False, cookies: bool = False, ) -> list[str]: command = _base_ytdlp_command() if remote_components: command.extend(["--remote-components", "ejs:github"]) ``` ### Technical Analysis The YouTube provider automatically retries failed operations with `yt-dlp --remote-components ejs:github`. This option permits `yt-dlp` to retrieve an external EJS component from GitHub and use it as part of video extraction. The remote component is not included in the audited Skill package, and this code does not pin it to a cryptographic digest or audited immutable artifact. Consequently, the effective extraction code can change after the Skill itself has been reviewed. Trust is delegated to the installed `yt-dlp` implementation, GitHub delivery path, remote repository state, and the remote component publisher. This behavior differs from downloading video content: it retrieves code or code-like extraction logic that participates in p ...[truncated 1303 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid automatic remote-component retrieval. Fail with an actionable message and require explicit authorization before retrying with remote components. 2. Prefer a locally installed and audited component distributed with a pinned `yt-dlp` release. 3. If remote retrieval is unavoidable: - Pin the component to an immutable release or commit. - Verify a cryptographic digest before use. - Document the expected source, version, and checksum. - Reject unexpected redirects or source changes. 4. Run remotely sourced extraction logic in a restricted subprocess with: - A dedicated temporary directory. - No browser-cookie access. - Minimal environment variables. - Restricted filesystem permissions. - Network access limited to required YouTube and component endpoints. 5. Record the exact component version and verified digest in `metadata.json` for auditability. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/providers/xiaohongshu.py:51
Finding
Weak hostname validation and unrestricted redirects permit unintended network destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/providers/xiaohongshu.py:51-53`, `scripts/providers/xiaohongshu.py:390-443`, `scripts/providers/douyin.py:33-35`, and `scripts/providers/douyin.py:231-236` **Vulnerability Type**: Server-side request forgery and insufficient URL validation **Risk Level**: Medium ### Complete Code Snippet ```python # scripts/providers/xiaohongshu.py:51-53 def supports(url: str) -> bool: host = urlparse(url).netloc.lower() return any(domain in host for domain in ("xiaohongshu.com", "xhslink.com", "xhslink.cn")) ``` ```python # scripts/providers/xiaohongshu.py:390-418 def _extract_best_direct_url(metadata: dict) -> tuple[str | None, str | None]: """Extract the highest-quality direct video URL from yt-dlp metadata. Checks (in order): formats list, requested_formats, direct url field. Returns: (url, source_field_name) or (None, None). """ formats = metadata.get("formats") or [] # Pick best format: prefer mp4/h264, highest resolution best = None best_score = -1 for fmt in formats: fmt_url = fmt.get("url") if not fmt_url: continue # Score: prefer video formats, higher resolution score = 0 if fmt.get("vcodec") and fmt.get("vcodec") != "none": score += 100 if fmt.get("acodec") and fmt.get("acodec") != "none": score += 10 height = fmt.get("height") or 0 score += height if score > best_score: best_score = score best = fmt if best: return best.get("url"), f"formats[{best.get('format_id', '?')}]" ``` ```python # scripts/providers/xiaohongshu.py:421-443 def _http_download(url: str, destination: Path, headers: dict[str, str]) -> None: """Download a file via HTTP with atomic temp-file replacement. Follows redirects automatically (urllib default). """ request = Request(url, headers=headers) try: with urlopen(requ ...[truncated 4231 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate `urlparse(url).hostname`, not `netloc`. 2. Require exact domains or proper subdomains: ```python def is_allowed_host(host: str, domain: str) -> bool: host = host.rstrip(".").lower() domain = domain.rstrip(".").lower() return host == domain or host.endswith("." + domain) ``` 3. Restrict accepted schemes to `https` unless a documented platform requirement mandates otherwise. 4. Before every connection: - Resolve all destination addresses. - Reject loopback, private, link-local, multicast, unspecified, and reserved addresses. - Explicitly block common cloud metadata addresses. - Defend against DNS rebinding by connecting only to a validated resolved address where feasible. 5. Disable automatic redirects or implement a custom redirect handler that revalidates the scheme, hostname, port, and resolved IP address at every hop. 6. Apply the same validation to: - User-provided share URLs. - `webpage_url` and `original_url`. - Every URL extracted from `formats`, `requested_formats`, or metadata `url` fields. 7. Define platform-specific media CDN allowlists where maintainable. If dynamic CDN hosts are necessary, use a narrowly documented policy instead of unrestricted Internet destinations. 8. Add tests covering deceptive hostnames, username-in-host syntax, trailing dots, mixed case, non-HTTPS schemes, redirects to private addresses, and DNS rebinding scenarios. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (44)

Tainted flow: 'request' from os.environ.get (line 368, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(request, timeout=600) as response:
            response_text = response.read().decode("utf-8", errors="replace")
    except HTTPError as exc:
        detail = _redact_secret(
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
venv/

# Secrets and browser credentials
.env
.env.*
!.env.example
cookies.txt
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

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

High
Category
YARA Match
Content
<视频链接>
```

推荐在 Agent 的项目规则中提前约定:

- 默认输出目录;
- 是否启用 ASR;
- 默认 ASR 后端和语言;
- whisper.cpp 模型路径;
- 是否允许读取浏览器 Cookie;
- 是否允许安装或升级系统工具。

## Cookie 与隐私

部分平台无法完全匿名下载。对应 Provider 可能在公开路线失败后调用:

```text
yt-dlp --cookies-from-browser chrome
```

这会读取本机 Chrome Cookie;在 macOS 上还可能触发钥匙串授权。项目不会生成 `cookies.txt`,也不会主动将 Cookie 内容写入输出文件,但下载命令本身仍会访问浏览器凭证。

在运行前请确认:

- 你理解并授权本次浏览器 Cookie 访问;
- 不把 Cookie 数据库、`cookies.txt` 或浏览器配置复制进仓库;
- 远程或无人值守环境中设置 `VIDEO_DOWNLOADER_REMOTE=1`,使小红书 Provider 在需要 Cookie 时直接报告;
- 只下载你拥有、获准下载或依法可以保存的�
Confidence
91% confidence
Finding
The README explicitly documents a fallback path using `yt-dlp --cookies-from-browser chrome`, which accesses local browser authentication material. Even though the text warns about consent and says cookies are not exported, this workflow still authorizes the skill to read sensitive browser credentials and could expose session data or enable unintended authenticated downloads if invoked in an agent context.

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

High
Category
YARA Match
Content
on metadata.

## Xiaohongshu Provider

The Xiaohongshu provider uses a three-tier download fallback:

1. **Anonymous yt-dlp** — public download without any authentication (no cookies).
2. **Public direct URL** — if metadata is available but anonymous yt-dlp can't download, extracts the highest-quality video stream URL from yt-dlp's formats metadata and downloads it via plain HTTP.
3. **Chrome cookies yt-dlp** — last resort; requires local Chrome browser access.

Metadata extraction is always anonymous — cookies are never triggered just to get author info or metadata.

Important behavior:

- Support `xiaohongshu.com`, `xhslink.com`, and `xhslink.cn` links.
- Store the Xiaohongshu title plus note body in `post_caption.txt`.
- Store `yt-dlp` raw metadata and normalized fields in `metadata.json`.
- Author nickname is taken from anonymous metadata; falls back to `未知作者` when unavailable.
- **Remote assistant**: if both public routes fail, reports "需要在电脑端授权 Chr
Confidence
93% confidence
Finding
The documented fallback to Chrome cookies enables the skill to access browser-authenticated session data to retrieve content. Although framed as a download fallback, browser cookie access is highly sensitive because it can expose authenticated resources and normalize a pattern similar to credential or session-token harvesting if not tightly constrained and explicitly approved.

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

High
Category
YARA Match
Content
"duration_seconds": normalized.get("video", {}).get("duration_seconds"),
        "resolution": normalized.get("video", {}).get("resolution"),
        "download_method": normalized.get("download", {}).get("method"),
    }


def _extract_metadata(url: str) -> dict:
    yt_dlp = _require_ytdlp()
    commands = [
        [yt_dlp, "--no-playlist", "--dump-single-json", url],
        [yt_dlp, "--cookies-from-browser", "chrome", "--no-playlist", "--dump-single-json", url],
    ]
    return json.loads(_run_first_successful(commands))


def _download_video(url: str, folder: Path, filename: str) -> Path:
    yt_dlp = _require_ytdlp()
    output_path = folder / filename
    commands = [
        [
            yt_dlp,
            "--no-playlist",
            "-f",
            "bv*+ba/b",
            "--merge-output-format",
            "mp4",
            "-o",
            str(output_path),
            url,
        ],
        [
            yt_dlp,
            "--cookies-from-browser",
Confidence
92% confidence
Finding
The YARA match is triggered by the use of --cookies-from-browser chrome, which resembles credential-harvesting behavior because it programmatically accesses browser-stored authentication material. Although the apparent purpose is content access rather than outright theft, in an agent-executed environment this is still dangerous because it can silently leverage sensitive browser sessions and normalize infostealer-like behavior.

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

High
Category
YARA Match
Content
)
                shutil.copyfileobj(response, temp_file)
        temp_path.replace(destination)
    except (HTTPError, URLError) as exc:
        raise RuntimeError(f"Failed to download Douyin video: {exc}") from exc


def _extract_metadata_with_ytdlp(url: str) -> dict:
    yt_dlp = _require_ytdlp()
    commands = [
        [yt_dlp, "--no-playlist", "--dump-single-json", url],
        [yt_dlp, "--cookies-from-browser", "chrome", "--no-playlist", "--dump-single-json", url],
    ]
    return json.loads(_run_first_successful(commands))


def _download_with_ytdlp(url: str, folder: Path, filename: str) -> Path:
    yt_dlp = _require_ytdlp()
    output_path = folder / filename
    commands = [
        [
            yt_dlp,
            "--no-playlist",
            "-f",
            "bv*+ba/b",
            "--merge-output-format",
            "mp4",
            "-o",
            str(output_path),
            url,
        ],
        [
            yt_dlp,
            "--cookies-from-browser",
Confidence
85% confidence
Finding
The info-stealer signature is triggered because the code reads browser cookies from Chrome, which is a sensitive credential-adjacent action. While this is not overt malware by itself and appears aimed at downloader fallback behavior, the capability is still dangerous because it harvests session material from the local browser context without strong scoping or consent controls.

Credential Access

High
Category
Privilege Escalation
Content
# Regex patterns for cookie auth failure detection
_COOKIE_FAILURE_PATTERNS = [
    r"keychain",
    r"Keychain",
    r"cannot (access|open|read).*cookie",
    r"permission denied.*cookie",
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Regex patterns for cookie auth failure detection
_COOKIE_FAILURE_PATTERNS = [
    r"keychain",
    r"Keychain",
    r"cannot (access|open|read).*cookie",
    r"permission denied.*cookie",
    r"cookies?.+not found",
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

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

High
Category
YARA Match
Content
-----------------------------------------------------

def _try_cookie_ytdlp_download(
    url: str,
    folder: Path,
    filename: str,
) -> tuple[Path | None, str | None, bool, dict | None]:
    """Try yt-dlp with Chrome cookies.

    Returns: (video_path, error, cookie_refused)
    """
    yt_dlp = _require_ytdlp()
    output_path = folder / filename
    command = [
        yt_dlp,
        "--cookies-from-browser", "chrome",
        "--no-playlist",
        "-f", "bv*+ba/b",
        "--merge-output-format", "mp4",
        "-o", str(output_path),
        url,
    ]
    try:
        completed = subprocess.run(
            command,
            check=False,
            capture_output=True,
            text=True,
            timeout=900,
        )
        stderr = completed.stderr.strip() or ""
        stdout = completed.stdout.strip() or ""

        if completed.returncode == 0:
            found = _find_output_file(output_path, folder)
            is_valid, validation = _validate_vide
Confidence
97% confidence
Finding
Using `yt-dlp --cookies-from-browser chrome` causes the skill to reach into the local Chrome profile and reuse authenticated browser cookies for downloads. In this skill context, that is materially sensitive because it bridges user-triggered content retrieval with access to browser-held credentials, potentially exposing private content access and normalizing implicit credential use without strong user consent boundaries.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The provider enables yt-dlp remote component loading from GitHub via --remote-components ejs:github, causing runtime retrieval of external code/components beyond the stated download function. That creates a supply-chain and remote-code trust risk because execution behavior can change based on network-fetched content outside the repository and outside normal review controls.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The provider enables yt-dlp remote component loading from GitHub via --remote-components ejs:github, causing runtime retrieval of external code/components beyond the stated download function. That creates a supply-chain and remote-code trust risk because execution behavior can change based on network-fetched content outside the repository and outside normal review controls.

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

High
Category
YARA Match
Content
solution": normalized.get("video", {}).get("resolution"),
        "download_method": normalized.get("download", {}).get("method"),
    }


def _extract_metadata(url: str) -> dict:
    commands = [
        _base_ytdlp_command() + ["--dump-single-json", url],
        _base_ytdlp_command() + ["--remote-components", "ejs:github", "--dump-single-json", url],
        _base_ytdlp_command()
        + ["--cookies-from-browser", "chrome", "--dump-single-json", url],
    ]
    return json.loads(_run_first_successful(commands))


def _download_video(url: str, folder: Path, filename: str) -> Path:
    output_path = folder / filename
    commands = [
        _download_command(output_path) + [url],
        _download_command(output_path, remote_components=True) + [url],
        _download_command(output_path, cookies=True) + [url],
    ]
    _run_first_successful(commands, timeout=1200)
    if output_path.exists():
        return output_path

    matches = sorted(folder.glob(f"{output_path.stem}.*"))
Confidence
90% confidence
Finding
The YARA match is triggered by direct access to browser cookies via --cookies-from-browser chrome, which overlaps with credential-harvesting patterns because it reads local authenticated session material. In this specific code there is no separate evidence of exfiltration logic, so it is better characterized as risky credential/session access rather than a confirmed stealer, but it is still a real security concern.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The README’s operational instructions, warnings, and usage guidance are presented only in Chinese. Under the policy for natural-language violations, forcing a specific language without user opt-in or a documented justification is a reportable issue.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill describes capabilities that require shell, network, filesystem, and environment access, but it does not declare any explicit tool scope or permission boundaries. This creates an authorization gap where a host agent may execute a powerful downloader/transcriber workflow without clear least-privilege controls or user-visible consent around network access, file writes, and environment-secret usage.

External Transmission

Medium
Category
Data Exfiltration
Content
### SiliconFlow (`--asr siliconflow`)

Requires `SILICONFLOW_API_KEY`. Calls `https://api.siliconflow.cn/v1/audio/transcriptions`.

Output artifacts:
- `audio.mp3`
Confidence
88% confidence
Finding
The skill supports sending extracted audio to a third-party cloud ASR endpoint, which is an external data transmission of potentially sensitive media content. Even if this is intended functionality, it can leak confidential speech, personal data, or regulated content when users or agents do not clearly opt in to cloud processing.

External Transmission

Medium
Category
Data Exfiltration
Content
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

SILICONFLOW_TRANSCRIPTION_URL = "https://api.siliconflow.cn/v1/audio/transcriptions"
SILICONFLOW_DEFAULT_MODEL = "FunAudioLLM/SenseVoiceSmall"

# whisper.cpp command and model paths are configurable via environment variables.
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
84% confidence
Finding
The manifest description presents platform-specific video downloading and metadata/caption extraction, with implementation details focused on H5 and yt-dlp routes for supported platforms. This module implements a full audio extraction and ASR pipeline, including backend selection, transcript/SRT generation, and raw transcription artifact writing, which is a materially broader behavior than the manifest's implementation notes describe.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
This code can upload extracted audio content to a third-party service using an environment-provided API key, which creates a real privacy and data-governance risk. Videos may contain sensitive speech or personal data, and the transfer occurs at the code level without any built-in policy gate, consent check, or content classification before exfiltrating audio off-host.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code uploads local audio to a remote transcription endpoint but contains no user-facing warning or consent mechanism in this file. In a downloader/transcription skill, that matters because users may reasonably expect local processing unless remote transfer is made explicit, especially when handling creator content, voice data, or regulated material.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The default prompt instructs the model to use Simplified Chinese and explicitly not use Traditional Chinese whenever the language is Chinese, zh, or Mandarin. This imposes a locale/script preference automatically rather than offering the user a choice, which is a natural-language policy violation under the language/locale rule.

Tainted flow: 'data' from pathlib.Path.read_text (line 125, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
return
    data = json.loads(path.read_text(encoding="utf-8"))
    data["asr"] = asr_result
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


if __name__ == "__main__":
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The code falls back to yt-dlp --cookies-from-browser chrome for metadata retrieval, which causes the tool to access local browser authentication cookies without any explicit user consent or warning at this call site. In an agent skill context, that can silently use the operator's logged-in session to access private or account-scoped content and may expose sensitive session-derived data to downstream processing or logs.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run_first_successful(commands: list[list[str]], *, timeout: int = 240) -> str:
    errors = []
    for command in commands:
        completed = subprocess.run(
            command,
            check=False,
            capture_output=True,
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
def _run_first_successful(commands: list[list[str]], *, timeout: int = 240) -> str:
    errors = []
    for command in commands:
        completed = subprocess.run(
            command,
            check=False,
            capture_output=True,
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
def _run_first_successful(commands: list[list[str]], *, timeout: int = 240) -> str:
    errors = []
    for command in commands:
        completed = subprocess.run(
            command,
            check=False,
            capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.