Back to skill

Security audit

Doubao Podcast TTS

Security checks for vulnerabilities and agentic risk

Overview

This Doubao podcast skill is mostly purpose-aligned, but it needs review because its batch generator can write MP3 files outside the chosen output folder and its trigger scope is broader than its vendor-specific implementation.

Review before installing. Use this only for Doubao/ByteDance podcast TTS work, avoid processing sensitive articles or text unless you accept sending them to ByteDance, store generated MP3s in a controlled location, and do not run batch JSON from untrusted sources until file IDs are validated to prevent path traversal. Prefer a reviewed commit and pinned dependencies.

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/generate_podcast.py:382
Finding
Batch Item Identifier Allows Arbitrary File Writes Outside the Output Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_podcast.py:382-388` **Vulnerability Type**: Path traversal and unrestricted file overwrite **Risk Level**: Medium ### Vulnerable Code ```python file_id = item.get("id", f"podcast_{i:03d}") url = item.get("url") text = item.get("text") category = item.get("category", "") output_path = os.path.join(output_dir, f"{file_id}.mp3") result = await generate_podcast( url=url, text=text, output_path=output_path, timeout=timeout, on_progress=on_progress, ) ``` The resulting path is subsequently created and overwritten: ```python os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) with open(output_path, "wb") as f: for chunk in audio_chunks: f.write(chunk) ``` ### Technical Analysis The batch item `id` is read directly from an externally supplied JSON file and interpolated into a filesystem path without validation or canonicalization. `os.path.join()` does not enforce containment beneath `output_dir`. An identifier containing parent-directory components, such as `../../shared/recording`, escapes the configured output directory. On supported platforms, an absolute identifier can also cause `os.path.join()` to discard the preceding output directory entirely. The program creates missing parent directories and opens the destination using `"wb"`, which truncates an existing file before writing generated audio. The `.mp3` suffix limits the names that can be targeted but does not prevent unauthorized writes or overwrites of writable files ending in that suffix. ### Attack Path 1. An attacker creates or modifies a batch JSON file accepted through `--batch`. 2. The attacker sets an item identifier to a traversal value, for example: ```json { "id": "../../shared/recording", "text": "Attacker-selected audio content" } ``` 3. The operator runs the batch generator with an output directory such as `./podcasts`. 4. The application constr ...[truncated 870 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict `file_id` to a conservative allowlist: ```python import re SAFE_ID = re.compile(r"^[A-Za-z0-9_-]+$") if not isinstance(file_id, str) or not SAFE_ID.fullmatch(file_id): raise ValueError("Invalid batch item identifier") ``` 2. Resolve and verify the destination remains beneath the output directory: ```python from pathlib import Path base_dir = Path(output_dir).resolve() output_path = (base_dir / f"{file_id}.mp3").resolve() if output_path.parent != base_dir: raise ValueError("Output path escapes the configured directory") ``` 3. Explicitly reject absolute paths, `..` components, directory separators, control characters, and platform-specific alternate separators. 4. If replacing files is not required, open new outputs in exclusive creation mode (`"xb"`) or require explicit confirmation before overwriting an existing destination. 5. Validate the complete batch document against a schema before processing it, including identifier type, maximum length, and permitted characters. ]]>

T08 · Insecure Dependencies

Note
Location
README.md:14
Finding
Installation Instructions Use Mutable and Unpinned Third-Party Sources<![CDATA[ ## Vulnerability Details **File Locations**: `README.md:14`, `README.md:79`, `README.md:83`, `scripts/generate_podcast.py:10`, and `scripts/generate_podcast.py:36` **Vulnerability Type**: Unpinned dependencies and mutable source installation **Risk Level**: Low ### Vulnerable Code The documented Skill installation clones the current state of a mutable repository: ```bash git clone https://github.com/mileszhang001-boom/doubao-podcast-skill.git ~/.claude/skills/doubao-podcast ``` Python and Node.js dependencies are installed without version or integrity constraints: ```bash pip install websockets npm install ws ``` The script repeats the unpinned Python installation instruction: ```python print("Please install websockets: pip install websockets") ``` ### Technical Analysis The installation process does not identify a reviewed Git commit, signed release, exact package version, lockfile, or cryptographic package hash. Consequently, the code installed by a user can differ from the snapshot covered by this audit. Cloning the default branch trusts all future changes made through the repository account. Installing the latest package version also trusts whatever version the package registry resolves at installation time. This creates a supply-chain exposure in which upstream compromise, account takeover, malicious package publication, or an unexpected incompatible release can change the effective code executed by users. No evidence was found that the current `websockets` or `ws` packages are malicious. The issue is the absence of reproducible and integrity-verified installation controls. ### Attack Path 1. An attacker compromises the referenced source repository, its maintainer account, the dependency publishing account, or the relevant package distribution channel. 2. The attacker publishes altered Skill content or a malicious dependency release. 3. A user follows the documented `git clone`, `pip install websockets`, or `npm install ws` instruct ...[truncated 764 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Direct users to a reviewed, immutable Git commit or signed release rather than an unconstrained default branch: ```bash git clone https://github.com/mileszhang001-boom/doubao-podcast-skill.git cd doubao-podcast-skill git checkout --detach <reviewed-commit-hash> ``` 2. Publish release checksums or signatures and document how users should verify them. 3. Pin Python dependencies to reviewed versions and hashes in a requirements file: ```text websockets==<reviewed-version> --hash=sha256:<verified-hash> ``` Install with: ```bash pip install --require-hashes -r requirements.txt ``` 4. Add a committed Node.js lockfile and use deterministic installation: ```bash npm ci ``` 5. Use automated dependency scanning and controlled update reviews. Re-audit dependency upgrades and Skill releases before changing pinned versions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The core declared purpose mostly matches the code: it is indeed a Doubao/ByteDance podcast TTS implementation that parses WebSocket binary frames, handles streaming audio, extracts audio_url from PodcastEnd, and explicitly works around timeout/stuck SessionFinished behavior. However, the description is materially broader than the actual code in several areas. The code does not implement browser-app integration patterns, caching logic, or article metadata extraction. More importantly, the description says the skill should be used for any podcast TTS or podcast generation task even without Doubao mention, while the code is tightly coupled to a specific ByteDance endpoint, header scheme, frame protocol, and event IDs. That trigger scope and some declared capabilities are broader than what the supplied code actually supports, so this should be flagged as a mismatch.

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger text is overly broad and instructs use for generic podcast TTS tasks even without Doubao context, which can cause misrouting of unrelated requests into a skill that assumes a specific vendor protocol and authentication model. In an automated agent setting, over-broad routing increases the risk of inappropriate handling of secrets, incorrect API usage, and file-persistence behavior being applied where it was not intended.

Session Persistence

Medium
Category
Rogue Agent
Content
| 4 | Missing `ping_timeout=120` | WebSocket disconnects during generation |
| 5 | Discarding audio chunks on disconnect | Losing already-generated content |
| 6 | `audio_url` expires in 24 hours | CDN returns 403 after expiry |
| 7 | Python stdout buffering with nohup | No real-time logs |
| **8** | **Browser WebSocket can't set custom headers** | **Must use server-side proxy** |
| **9** | **`duration_sec` often returns 0** | **Need to estimate from audio size** |
| **10** | **RoundStart round 1 text is empty** | **Head music, real content from round 2** |
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| 4 | Missing `ping_timeout=120` | WebSocket disconnects during generation |
| 5 | Discarding audio chunks on disconnect | Losing already-generated content |
| 6 | `audio_url` expires in 24 hours | CDN returns 403 after expiry |
| 7 | Python stdout buffering with nohup | No real-time logs |
| **8** | **Browser WebSocket can't set custom headers** | **Must use server-side proxy** |
| **9** | **`duration_sec` often returns 0** | **Need to estimate from audio size** |
| **10** | **RoundStart round 1 text is empty** | **Head music, real content from round 2** |
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.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This markdown file documents behavior that can affect user data and privacy: content is sent through a server-side proxy to Doubao, articles may be scraped, and audio/content may be cached locally. Under the markdown-specific warning criterion, the description should explicitly disclose these behaviors and their implications, but no such warning is present in the README.

Lp3

Medium
Category
MCP Least Privilege
Confidence
82% confidence
Finding
The skill describes implementations that rely on environment-held credentials, local file persistence, and reading/writing generated content, but it declares no explicit tool scope or permissions boundary. In an agent ecosystem, this can cause the skill to be invoked with broader-than-expected capabilities, increasing the chance of unintended secret access or file-system writes without clear user consent.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The guidance explicitly recommends downloading generated MP3s to local storage but does not warn about persistence, path handling, retention, or access controls. This can lead implementers to store potentially sensitive generated audio on disk by default, creating privacy exposure and increasing the blast radius if the host is shared or compromised.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The recommended production design caches source URLs, titles, audio files, and durations without any privacy, consent, or retention guidance. Even if operationally useful, retaining source-linked media and metadata can create a durable record of user activity or content that may be sensitive, especially in multi-tenant or regulated environments.

Natural-Language Policy Violations

Low
Confidence
76% confidence
Finding
The file presents core descriptive and instructional content in Chinese alongside English, but does not indicate language options or user choice. If organizational policy requires avoiding forced language/locale assumptions, this can be a natural-language policy issue because some users may not be able to follow the guidance.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
This code file contains its primary docstring, CLI description, and usage guidance entirely in Chinese, which imposes a specific language on users without any opt-in or alternative locale. The policy allows locale constraints only when explicitly justified or when users are given a choice, neither of which is present here.

Static analysis

No suspicious patterns detected.