Back to skill

Security audit

Audiobook Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to make audiobooks as advertised, but it needs review because it downplays first-run network downloads and uses unpinned install commands that can change what code runs.

Review this before installing in offline, sensitive, or policy-restricted environments. Pre-download and verify the model assets if you need no runtime network access, pin and hash dependencies where possible, and run it only on manuscripts and output directories you intentionally choose.

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)

T08 · Insecure Dependencies

Warning
Location
README.md:40
Finding

Unpinned Third-Party Dependencies Allow Supply-Chain Version Drift

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/verify_job.py:54
Finding

Manifest-Controlled Paths Can Escape the Selected Job Directory

Content
View full analysis
TOLERANCE_SEC: fails.append(f"duration mismatch: master {asm}s vs segments {seg_sum}s") if not decodes(master): fails.append("master WAV fails full decode") mp3_name = m["outputs"].get("mp3") if mp3_name: mp3 = d / mp3_name ``` The resulting paths are opened locally or passed to FFmpeg: ```python def wav_duration(path: Path) -> float: with wave.open(str(path), "r") as w: return w.getnframes() / float(w.getframerate()) def decodes(path: Path) -> bool: r = subprocess.run(["ffmpeg", "-v", "error", "-i", str(path), "-f", "null", "-"], capture_output=True, text=True, check=False) return r.returncode == 0 and not r.stderr.strip() ``` ### Technical Analysis The verifier treats the contents of `manifest.json` as trusted paths. Values such as `chunks[].segment`, `outputs.master_wav`, and `outputs.mp3` are joined with the user-selected job directory without normalization or containment validation. In `pathlib`, an absolute right-hand operand replaces the preceding base path. Relative values containing `../` can also traverse outside the job directory after filesystem res ...[truncated 1892 chars]
Remediation
View remediation
Path: candidate_value = Path(value) if candidate_value.is_absolute(): raise ValueError(f"absolute manifest path is forbidden: {value}") candidate = (job_root / candidate_value).resolve() if not candidate.is_relative_to(job_root): raise ValueError(f"manifest path escapes job directory: {value}") return candidate ``` 2. Apply this function to every `chunks[].segment`, `outputs.master_wav`, and `outputs.mp3` value. 3. Reject non-string path values, empty paths, absolute paths, and traversal components as malformed manifest data. 4. Consider rejecting symlinks, or validate the fully resolved target immediately before use, if jobs may come from untrusted sources. 5. Validate the complete manifest against a strict schema before indexing nested fields. 6. Restrict segment files to the expected `segments/` directory and expected filename pattern, such as `segments/seg_XXXX.wav`. 7. Handle validation and media-parser exceptions cleanly so malformed jobs fail verification without an uncontrolled traceback. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (15)

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The README makes a strong 'no network at run time' and 'fully local' claim, but later admits the model and voice assets are fetched automatically on first synthesis from HuggingFace. This is a real documentation integrity issue because users may run the skill in offline, restricted, or sensitive environments based on that claim and unintentionally trigger outbound network access or fail at runtime.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This is the same underlying issue as SDI-1: the documentation presents the workflow as having no runtime network dependency, while first use actually performs automatic downloads. In security-conscious deployments, misleading network-behavior claims can cause policy violations, unreliable operation, or accidental trust in unvetted remote artifacts.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill describes and instructs use of file read/write and shell execution via Python scripts, but it declares no explicit tool scope or permissions. That increases the chance an agent platform grants broader capabilities than intended or invokes the skill without sufficient user visibility into its access needs.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger phrases are broad natural-language requests like 'read this aloud' and 'narrate this,' which could match ordinary user intent too loosely and cause unintended invocation. In a skill that writes output files and runs local scripts, accidental activation can lead to unnecessary file processing or command execution without the user explicitly choosing this workflow.

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

python
def probe_duration(path: Path) -> float:
    r = subprocess.run(
        ["ffprobe", "-v", "error", "-show_entries", "format=duration",
         "-of", "default=noprint_wrappers=1:nokey=1", str(path)],
        capture_output=True, text=True, check=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/assemble_video.py (reported line 56)May include surrounding context.

python
"-shortest", "-movflags", "+faststart",
        str(out),
    ]
    subprocess.run(cmd, check=True, capture_output=True, text=True)


def faststart_ok(path: Path) -> bool:

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

python
"print(json.dumps(d))"
    )
    try:
        r = subprocess.run([python_bin, "-c", probe],
                           capture_output=True, text=True, check=False)
        line = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else "{}"
        out.update(json.loads(line))

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

python
except Exception:
        out["python"] = "unknown"
    try:
        ff = subprocess.run(["ffmpeg", "-version"],
                            capture_output=True, text=True, check=False)
        out["ffmpeg"] = ff.stdout.splitlines()[0] if ff.stdout else "unknown"
    except FileNotFoundError:

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

python
"--output_path", str(seg_dir),
        "--file_prefix", prefix,
    ]
    r = subprocess.run(cmd, input=text, capture_output=True, text=True, check=False)
    if r.returncode != 0:
        raise RuntimeError(
            f"CLI exit {r.returncode}: {r.stderr.strip().splitlines()[-1:]}"

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

python
def preflight(python_bin: str, model: str) -> None:
    """Fail fast with a clear message if the engine is not importable."""
    r = subprocess.run(
        [python_bin, "-c", "import mlx_audio.tts.generate"],
        capture_output=True, text=True, check=False,
    )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
84% confidence
Finding

The script writes a concat list for ffmpeg using unescaped file paths and enables ffmpeg concat with -safe 0. If a segment path contains quotes or specially crafted characters, ffmpeg may misparse the list, potentially reading unintended files or failing in unsafe ways; because file names derive from filesystem paths under user-controlled output locations, this is a real file-parsing/injection surface even though no shell is used.

Content

Scanner excerpt · scripts/narrate.py (reported line 229)May include surrounding context.

python
with open(listfile, "w") as f:
        for seg in segments:
            f.write(f"file '{Path(seg).resolve()}'\n")
    subprocess.run(
        ["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(listfile),
         "-c", "copy", str(master)],
        check=True, capture_output=True, text=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/narrate.py (reported line 238)May include surrounding context.

python
def encode_mp3(master: Path, mp3: Path):
    subprocess.run(
        ["ffmpeg", "-y", "-i", str(master),
         "-codec:a", "libmp3lame", "-qscale:a", "2", str(mp3)],
        check=True, capture_output=True, text=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/assemble_video.py (reported line 78)May include surrounding context.

python
def decodes(path: Path) -> bool:
    r = subprocess.run(["ffmpeg", "-v", "error", "-i", str(path), "-f", "null", "-"],
                       capture_output=True, text=True, check=False)
    return r.returncode == 0 and not r.stderr.strip()

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

python
def decodes(path: Path) -> bool:
    r = subprocess.run(["ffmpeg", "-v", "error", "-i", str(path), "-f", "null", "-"],
                       capture_output=True, text=True, check=False)
    return r.returncode == 0 and not r.stderr.strip()

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README does mention automatic downloads, but it does not elevate this as a prominent operational/security warning despite earlier emphasizing a local, no-network runtime. That omission can mislead users about egress behavior and supply-chain exposure from fetching model weights at execution time.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.