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<![CDATA[ ## Vulnerability Details **File Location**: `README.md:40-47` **Vulnerability Type**: Unpinned executable dependencies **Risk Level**: Medium ### Vulnerable Code ```bash pip install --upgrade pip # 3) engine + English G2P frontend pip install mlx-audio # pulls in the MLX Kokoro pipeline pip install "misaki[en]" # English grapheme-to-phoneme frontend # 4) ffmpeg (Homebrew) brew install ffmpeg ``` ### Technical Analysis The documented installation procedure installs the latest available versions of `pip`, `mlx-audio`, `misaki`, their transitive dependencies, and FFmpeg. The commands do not enforce the versions listed as tested elsewhere in the README, use a dependency lock file, or verify package hashes. Python packages may execute code during installation and are subsequently imported and invoked by `scripts/narrate.py`. Consequently, the effective executable dependency set can change after this project has been reviewed. A compromised upstream release, dependency-confusion event, malicious transitive dependency, or incompatible future release could therefore introduce code that was not included in the audited repository. This finding does not establish that any currently named dependency is malicious. The risk arises from executing versions selected dynamically at installation time without integrity verification. ### Attack Path 1. An attacker compromises an upstream package, one of its transitive dependencies, or the package-distribution account used to publish a new release. 2. The attacker publishes a malicious version under a dependency name used by the documented commands. 3. A user follows the installation instructions after that release becomes the default version. 4. `pip` downloads and installs the attacker-controlled package because no exact version or hash is required. 5. Malicious code executes during installation or when `narrate.py` imports and invokes the affected package. ### Impact Assessment Malicious dep ...[truncated 505 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct Python dependency to an exact reviewed version, matching the confirmed-good versions documented by the project. 2. Generate and commit a lock file that captures all transitive dependencies. 3. Use hash-verified installation, such as a requirements file generated with hashes and installed using: ```bash python -m pip install --require-hashes -r requirements.txt ``` 4. Avoid an unconstrained `pip install --upgrade pip` in the reproducible setup path. Pin the installer version or document it separately as an optional maintenance action. 5. Record the expected FFmpeg version and provide a mechanism to verify the installed binary and package source. 6. Use only trusted package indexes and explicitly document the expected index configuration. 7. Add automated dependency auditing and controlled update procedures so version changes receive review before being incorporated into installation instructions. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/verify_job.py:54
Finding
Manifest-Controlled Paths Can Escape the Selected Job Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/verify_job.py:54-78` **Vulnerability Type**: Path traversal through untrusted manifest entries **Risk Level**: Low ### Vulnerable Code ```python chunks = m.get("chunks", []) seg_paths = [d / c["segment"] for c in chunks] missing = [str(p) for p in seg_paths if not p.is_file()] if missing: fails.append(f"{len(missing)} segment file(s) missing: {missing[:3]}") if m.get("chunk_count") != len(chunks): fails.append("chunk_count != len(chunks)") present = [p for p in seg_paths if p.is_file()] seg_sum = round(sum(wav_duration(p) for p in present), 2) if present else 0.0 master = d / m["outputs"]["master_wav"] if not master.is_file(): fails.append(f"master WAV missing: {master.name}") else: asm = round(wav_duration(master), 2) if abs(asm - seg_sum) > 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
<![CDATA[ ## Remediation Suggestions 1. Resolve the job directory once and validate every manifest-derived path against it before any existence check, file open, or subprocess invocation: ```python job_root = args.job_dir.resolve() def safe_job_path(value: str) -> 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
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.

Intent-Code Divergence

Medium
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.

Lp3

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.

Vague Triggers

Medium
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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,
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
"-shortest", "-movflags", "+faststart",
        str(out),
    ]
    subprocess.run(cmd, check=True, capture_output=True, text=True)


def faststart_ok(path: Path) -> bool:
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
"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))
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
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:
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
"--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:]}"
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 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,
    )
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
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,
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
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,
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 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()
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 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()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
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.

Static analysis

No suspicious patterns detected.