Back to skill

Security audit

YouTube to Podcast (小宇宙)

Security checks for vulnerabilities and agentic risk

Overview

ClawScan marks this skill suspicious because scanners report an unauthenticated local file server, unclear upload authorization and triggers, and a mismatch between its stated podcast purpose and some documented actions.

Consider the suspicious verdict and the incomplete analysis before installing. Pay particular attention to the reported possibility that unrelated websites could read files being prepared for upload, and to the unclear conditions for starting uploads and sending notifications. The scan does not establish malicious intent, but it also does not establish that the skill is safe.

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

T09 · Insecure Skill Coding Practices

Warning
Location
adapters/xiaoyuzhou/upload.js
Finding

Unauthenticated loopback file server permits reads from unrelated websites

Content
View full analysis

Impact

An attacker can obtain the audio and optional cover before their intended publication, including content not yet approved or available publicly. This does not expose arbitrary filesystem paths.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (31)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/worker.py (reported line 714)May include surrounding context.

python
url = "https://i.ytimg.com/vi/%s/%s" % (video_id, name)
        try:
            request = urllib.request.Request(url, headers=headers)
            with urllib.request.urlopen(request, timeout=30) as response:
                payload = response.read()
        except Exception as exc:  # noqa: BLE001 - 封面失败不致命
            log(job, "下载封面失败(%s): %s" % (name, exc))

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

虽然安装 CLI 可以是支持性实现细节,但登录并发布技能属于独立的分发行为,未被声明用途涵盖。此代码片段未执行 YouTube 处理、NotebookLM 音频生成、ffmpeg 转码、小宇宙上传或结果通知。该结论仅针对提供的 README 命令,不推断未展示的技能实现。

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The command defaults draft to true and calls /v1/episode/hosted/publish only when draft is false. Consequently, uploading alone does not implement the declared automatic publication workflow. A caller could supply --publish, but no such orchestration is shown in this chunk. Local file access, the temporary loopback server, authenticated platform requests, and browser form automation support the declared upload purpose and are not independent mismatches. Because this is an upload adapter, the absence of YouTube processing, NotebookLM generation, ffmpeg transcoding, and notifications here does not by itself establish additional mismatches.

Content

No source excerpt is available for this finding.

Default uploads bypass the stated publication-authorization boundary by treating an auto-publishing workflow as a draft.

High
Category
Prompt Injection
Confidence
99% confidence
Finding

The instructions claim public release requires --publish plus an environment variable, yet default uploads enter a review process that can automatically publish without either control. Contradictory assurances that drafts are private can cause the agent or user to authorize what appears to be private processing but actually produces an irreversible public action.

Content

No source excerpt is available for this finding.

Incomplete referenced artifact analysis

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 213)May include surrounding context.

md
| `NEED_LOGIN` | NotebookLM / 小宇宙掉登录 | 先跑 `scripts/doctor.sh` 看是哪一侧掉了,登录后 `--rerun` |

Context Leakage

High
Category
Data Exfiltration
Confidence
85% confidence
Finding

Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Content

Scanner excerpt · adapters/xiaoyuzhou/upload.js (reported line 314)May include surrounding context.

js
}

/**
 * The upload dialog is a Mantine modal whose content is mounted from a
 * requestAnimationFrame driven transition. Chrome does not run rAF for hidden
 * pages, so a backgrounded tab would leave the dialog empty forever.
 */

Context Leakage

High
Category
Data Exfiltration
Confidence
85% confidence
Finding

Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Content

Scanner excerpt · adapters/xiaoyuzhou/upload.js (reported line 345)May include surrounding context.

js
}
    throw new CommandExecutionError(
        'The Xiaoyuzhou podcaster page is not visible (Chrome window is minimised or another tab is in front)',
        'The upload dialog of the podcaster backend only renders in a visible page. Bring the Chrome window to the front (or run the command with --window foreground) and retry.',
    );
}

Context Leakage

High
Category
Data Exfiltration
Confidence
85% confidence
Finding

Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Content

Scanner excerpt · adapters/xiaoyuzhou/upload.js (reported line 380)May include surrounding context.

js
const visibility = await evaluate(page, '(() => document.visibilityState)()');
    if (visibility !== 'visible') {
        throw new CommandExecutionError(
            'The Xiaoyuzhou upload dialog stayed empty because the page is not visible',
            'Bring the Chrome window to the front (or run the command with --window foreground) and retry.',
        );
    }

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/worker.py (reported line 281)May include surrounding context.

python
"""子进程环境:补上代理,让 Node fetch 走得通。"""
    global CHILD_ENV, _PROXY_APPLIED
    if CHILD_ENV is None:
        env = dict(os.environ)
        proxy = system_proxy()
        if proxy:
            for key in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"):

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% 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.

Content

Scanner excerpt · CHANGELOG.md (reported line 129)May include surrounding context.

md
### Changed
- **公开发布改为双重显式授权**:`--publish` 且 `YT2PODCAST_ALLOW_PUBLISH=1`,缺一即报
  `PUBLISH_NOT_ALLOWED`。回应安全扫描"public publishing can occur without explicit authorization"。
- **不再自行 detach 进程**:`new-job.sh` 只写 job 文件并打印下一步命令,移除了 setsid/nohup 启动路径。
  回应"a worker can outlive the host's process cleanup"。
- 新增 `SECURITY.md`,逐项说明执行命令、网络、写入范围、发布授权、进程生命周期、无遥测。

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% 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.

Content

Scanner excerpt · SKILL.md (reported line 140)May include surrounding context.

md
不会重复消耗 NotebookLM 生成配额。

   > ⚠️ 关键坑:OpenClaw 的 exec 在回合结束时会 kill 整个进程组(日志里能看到
   > `treeOwnership: process-group`)。普通 `nohup ... &` 起的 worker 会被立刻杀掉,
   > job 永远停在第一个阶段,而且**不会**有失败通知。所以必须用宿主的 background 托管能力,
   > 不要自己 detach。

A bare YouTube link activates the full workflow without establishing conversion intent.

Medium
Category
Trigger Abuse
Confidence
96% confidence
Finding

The documented trigger treats receiving a YouTube link as authorization for generation and upload, without establishing that the user wants conversion or public distribution. Because the same source says uploaded episodes automatically become public after review, an ambiguous link-sharing message can cause an unintended external publication and consume generation quota.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% 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.

Content

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

md
## 已知坑(都已修,留档避免回归)

1. **worker 启动即被杀**:OpenClaw 的 exec 在回合结束时会 kill 整个进程组(`treeOwnership: process-group`),`nohup ... &` 不顶用,job 会永远停在第一阶段且**没有任何通知**。
   → 用 exec 的 `background: true` 托管;`new-job.sh` 的内部启动也加了 setsid 兜底。
2. **AddSources RPC 静默失败**:`youtu.be/xxx?si=...` 带追踪参数会让 NotebookLM 加源返回 no source id。
   → `canonical_youtube_url()` 统一成 `https://www.youtube.com/watch?v=<id>`。

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% 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.

Content

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

md
## 已知坑(都已修,留档避免回归)

1. **worker 启动即被杀**:OpenClaw 的 exec 在回合结束时会 kill 整个进程组(`treeOwnership: process-group`),`nohup ... &` 不顶用,job 会永远停在第一阶段且**没有任何通知**。
   → 用 exec 的 `background: true` 托管;`new-job.sh` 的内部启动也加了 setsid 兜底。
2. **AddSources RPC 静默失败**:`youtu.be/xxx?si=...` 带追踪参数会让 NotebookLM 加源返回 no source id。
   → `canonical_youtube_url()` 统一成 `https://www.youtube.com/watch?v=<id>`。
3. **下载音频 `fetch failed`**:音频在 `lh3.googleusercontent.com`,国内直连超时;opencli 用 Node fetch,不读 macOS 系统代理。

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

Scanner excerpt · SECURITY.md (reported line 51)May include surrounding context.

md
Nothing is detached: `scripts/new-job.sh` only writes a job file and prints the next
command. The worker runs under whatever supervisor the caller uses (agent host, terminal,
system service) and exits with it. No cron/systemd/launchd entries are installed.

## Telemetry

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
75% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · adapters/xiaoyuzhou/upload.js (reported line 16)May include surrounding context.

js
*   2. the file is attached with a synthetic `DataTransfer` + `change` event,
 *      which is exactly what react-dropzone listens for;
 *   3. the local file is served over a short-lived loopback HTTP server, so the
 *      browser reads it straight from disk (no base64 round trip, no size cap).
 *
 * Draft vs publish: creating an episode (POST /v1/episode/hosted/create-free)
 * only produces an unpublished draft (status INITIALIZED, shown as 单集审核中 in

The adapter defaults to an unpublished draft, contrary to the manifest's upload-to-review-and-auto-publication workflow.

Medium
Category
Excessive Agency
Confidence
91% confidence
Finding

The manifest states that uploading enters platform review and automatically goes live after approval, with --hold-upload as the pre-upload stopping point. Instead, L692 defaults --draft to true, L723 preserves that default, and L792–L799 skip the publish request unless draft mode is explicitly disabled; the module documentation at L018–L021 likewise describes this default outcome as unpublished.

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/resume-stalled.py (reported line 54)May include surrounding context.

python
def worker_running(job_id: str) -> Optional[int]:
    """worker.py <job_id> 还在跑就返回 pid。"""
    try:
        out = subprocess.run(
            ["pgrep", "-f", r"worker\.py %s(\s|$)" % job_id],
            stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10,
        ).stdout.decode()

Tainted flow: 'job_id' from pathlib.Path.read_text (line 101, file read) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

The job_id read from a job file is inserted into a pgrep regular expression without validation or escaping. Someone able to influence that file can alter process matching and suppress recovery through false liveness results; this is regex injection affecting availability, not shell or Python code execution.

Content

Scanner excerpt · scripts/resume-stalled.py (reported line 54)May include surrounding context.

python
def worker_running(job_id: str) -> Optional[int]:
    """worker.py <job_id> 还在跑就返回 pid。"""
    try:
        out = subprocess.run(
            ["pgrep", "-f", r"worker\.py %s(\s|$)" % job_id],
            stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10,
        ).stdout.decode()

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/resume-stalled.py (reported line 136)May include surrounding context.

python
tmp.write_text(json.dumps(job, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
        os.replace(tmp, path)
        print("[resume] %s -> %s" % (item["job_id"], target), file=sys.stderr)
        code = subprocess.call([sys.executable, str(WORKER), item["job_id"]])
        return code == 0
    print("[dry-run] %s would resume at %s" % (item["job_id"], target), file=sys.stderr)
    return True

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

python
if sys.platform != "darwin":
        return None
    try:
        out = subprocess.run(
            ["scutil", "--proxy"], stdout=subprocess.PIPE, timeout=5
        ).stdout.decode("utf-8", "replace")
    except (OSError, subprocess.SubprocessError):

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

python
def run_cmd(args: List[str], timeout: int, cwd: Optional[str] = None) -> Tuple[int, str, str]:
    """执行外部命令,带超时;超时不抛 Python 异常,交给调用方判断。"""
    try:
        proc = subprocess.run(
            args,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,

Tainted flow: 'args' from os.environ.get (line 691, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · scripts/worker.py (reported line 297)May include surrounding context.

python
def run_cmd(args: List[str], timeout: int, cwd: Optional[str] = None) -> Tuple[int, str, str]:
    """执行外部命令,带超时;超时不抛 Python 异常,交给调用方判断。"""
    try:
        proc = subprocess.run(
            args,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,

Tainted flow: 'output' from os.environ.get (line 401, credential/environment) → pathlib.Path.write_bytes (file write)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · scripts/worker.py (reported line 404)May include surrounding context.

python
output = Path(args[index + 1])
            if output:
                output.parent.mkdir(parents=True, exist_ok=True)
                output.write_bytes(b"RIFF----WAVEfmt " + b"0" * 1024)
        except OSError:
            pass
        return {"notebook_id": "dryrun-notebook-id", "file": "audio.wav", "bytes": 1024, "status": "done"}