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

Vague Triggers

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

Missing User Warnings

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

Natural-Language Policy Violations

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

Static analysis

No suspicious patterns detected.