Back to skill

Security audit

BookMorph Magic

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its stated book-packaging purpose, but its helper script can delete or write outside the intended output folder if configured unsafely.

Review before installing or using in automation. Only run the helper with a trusted, dedicated output root, avoid custom `--episode-dir-prefix` values, and do not use `--clear-existing` unless the target path has been independently checked. The package does not show exfiltration or persistence, but it needs path-containment and deletion safeguards before broad use.

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

Error
Location
scripts/episode_bundle.py:38
Finding

User-Controlled Episode Directory Prefix Allows Arbitrary Directory Deletion and Out-of-Root Writes

Content
View full analysis

Vulnerability Details

File Location: scripts/episode_bundle.py:38-39, 115-125, 139-143, 254-257, 281-282
Vulnerability Type: Path traversal and unrestricted filesystem operation
Risk Level: High

Vulnerable Code

python
def episode_dir(number: int, output_root: Path, prefix: str) -> Path:
    return output_root / f"{prefix}{episode_slug(number)}"
python
def command_prepare_dest(args: argparse.Namespace) -> int:
    number = normalize_episode_number(args.episode)
    output_root = ensure_absolute_path(args.output_root, label="output-root", require_exists=False)
    assert output_root is not None
    dest_dir = episode_dir(number, output_root, args.episode_dir_prefix)
    existed = dest_dir.exists()

    if existed and args.clear_existing:
        shutil.rmtree(dest_dir)

    dest_dir.mkdir(parents=True, exist_ok=True)
python
def command_bundle(args: argparse.Namespace) -> int:
    number = normalize_episode_number(args.episode)
    slug = episode_slug(number)
    output_root = ensure_absolute_path(args.output_root, label="output-root", require_exists=False)
    assert output_root is not None
    dest_dir = episode_dir(number, output_root, args.episode_dir_prefix)
    dest_dir.mkdir(parents=True, exist_ok=True)
python
prepare_dest.add_argument("--episode-dir-prefix", default=DEFAULT_EPISODE_DIR_PREFIX)
python
bundle.add_argument("--episode-dir-prefix", default=DEFAULT_EPISODE_DIR_PREFIX)

Technical Analysis

The --episode-dir-prefix argument is accepted without validation and is directly combined with the configured output root. The implementation does not reject absolute prefixes, directory separators, or .. traversal components, and it does not verify that the resulting destination remains beneath output_root.

In pathlib, joining a path with an absolute second operand discards the original base path. For example, if ` ...[truncated 2603 chars]

Remediation
View remediation

Remediation Suggestions

  1. Treat the prefix strictly as a filename prefix, not as a path. Reject absolute values, path separators, .., and empty or special path components.
  2. Apply an allowlist such as ^[A-Za-z0-9._-]+$, with an appropriate maximum length.
  3. Resolve both the output root and proposed destination before any filesystem operation, then enforce strict containment:
    python
    def safe_episode_dir(number: int, output_root: Path, prefix: str) -> Path:
        if not re.fullmatch(r"[A-Za-z0-9._-]+", prefix):
            raise SystemExit("episode-dir-prefix contains invalid characters")
    
        root = output_root.resolve(strict=False)
        destination = (root / f"{prefix}{episode_slug(number)}").resolve(strict=False)
    
        if destination == root or root not in destination.parents:
            raise SystemExit("episode destination escapes output-root")
    
        return destination
    
  4. Perform this validation immediately before every deletion, directory creation, copy, and manifest write so later code changes cannot bypass the boundary.
  5. Refuse recursive deletion of the output root itself and other protected locations. Consider requiring a marker file created by this application before clearing an existing directory.
  6. Add regression tests covering absolute prefixes, ../ traversal, nested separators, symbolic-link boundary cases, empty prefixes, and valid ordinary prefixes.
  7. Run the bundling process under a least-privileged account with write access restricted to the intended output root as defense in depth.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly instructs users to create or reset the target episode directory and shows prepare-dest --clear-existing, but it does not require confirmation prompts, safety checks, or warnings about destructive behavior. In a publishable template, this increases the risk that integrators or operators will clear the wrong path and lose data, especially because the output root is configurable and could be mis-set to a sensitive directory.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The default prompt uses a broad activation trigger: 'when the user wants to turn a book into a packaged set of content assets such as video, audio, and cover images.' This can match a wide range of ordinary book-related requests and may invoke the skill when the user did not explicitly ask for this orchestration workflow, causing unintended tool routing or content generation steps.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script deletes the target episode directory recursively with shutil.rmtree() whenever --clear-existing is supplied, without any confirmation prompt, safety interlock, or validation that the directory is confined to an expected safe base path. Because both output_root and episode_dir_prefix are user-controlled, a caller can cause deletion of arbitrary absolute paths under a chosen root, making accidental or abusive destructive deletion plausible in automation contexts.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The display metadata uses Chinese in the short description while the operational prompt is written in English, but there is no statement that the skill adapts to the user's preferred language. This can imply a language/locale behavior without explicit user opt-in or documented language selection.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.