How book-to-skill Ensures Security Against Untrusted Documents

book-to-skill treats every input file as potentially hostile by implementing defense-in-depth security including invisible Unicode sanitization, symlink-resistant directory hardening, strict file-type validation, and controlled error handling.

book-to-skill is an open-source document extraction pipeline designed to convert e-books and PDFs into structured text for downstream language model consumption. Because it processes content from potentially untrusted sources, the codebase implements rigorous security measures to prevent prompt injection, directory traversal, and data exfiltration attacks while maintaining robust error boundaries.

Defense-in-Depth Security Architecture

The security model in book-to-skill operates on the principle that no single check is sufficient for hostile inputs. The pipeline combines four complementary layers: invisible-character sanitization to neutralize hidden Unicode payloads, output-directory hardening to prevent filesystem-based attacks, file-type validation with magic sniffing to block malformed archives, and safety-first error handling to ensure exceptions never leak internal state or crash the process.

Invisible-Character Sanitization to Prevent Prompt Injection

Malicious actors often embed zero-width spaces, bidirectional overrides, and other invisible Unicode code points into documents to create "prompt injection" vectors—instructions that humans cannot see but language models will process. In book_to_skill/sanitize.py, the module defines comprehensive sets of dangerous code points including zero-width spaces (U+200B), bidirectional controls, invisible letters, annotation format controls, tag blocks, variation selectors, and musical format characters.

The function sanitize_extracted_text() (lines 39‑50) processes every extracted string through is_invisible_codepoint() to strip these characters before downstream processing. If sanitization removes any characters, the system emits a security warning via the logging infrastructure in book_to_skill/utils.py (lines 98‑102), alerting operators to potential tampering attempts.

from book_to_skill.sanitize import sanitize_extracted_text

raw = "Hello\u200BWorld"               # ZERO-WIDTH SPACE (U+200B) inserted

clean, removed = sanitize_extracted_text(raw)
print(clean)    # → "HelloWorld"

print(removed)  # → 1

Output Directory Hardening Against Directory Traversal

To prevent symlink attacks and unauthorized access, prepare_output_dir() in book_to_skill/utils.py (lines 35‑58) implements strict filesystem controls. The function first verifies that the target path is not a symbolic link using path.is_symlink(), aborting immediately with an ExtractionError if a symlink is detected.

On POSIX systems, the code validates that the directory is owned by the current user ID (UID) before setting restrictive 0o700 permissions (owner read/write/execute only). If the directory does not exist, it is created atomically with mode 0o700, ensuring no other user can read extracted content or race-condition the directory creation.

from pathlib import Path
from book_to_skill.utils import prepare_output_dir

work_dir = Path("/tmp/book-to-skill-work")
prepare_output_dir(work_dir)   # creates dir with mode 0o700,

                               # aborts if path is a symlink

File Type Validation and Magic Sniffing

Before parsing, book-to-skill validates that only supported, well-known formats enter the extraction pipeline. In extract_single_file() within book_to_skill/utils.py (lines 29‑61), the code checks file extensions against an internal SUPPORTED_EXTENSIONS whitelist.

If the extension is unknown or suspicious, the system reads the initial bytes to detect PDF headers (%PDF) or ZIP-based e-book structures (EPUB, DOCX). Unsupported or malformed archives trigger a clear ExtractionError before any parser consumes the content, preventing format confusion attacks and memory exhaustion from malicious archives.

Safety-First Error Handling

All extraction branches wrap low-level I/O operations in exception handlers that convert OSError, zipfile.BadZipFile, and similar errors into controlled ExtractionError exceptions (lines 34‑57). This pattern prevents stack traces from leaking internal path structures or implementation details while ensuring the main processing loop can log the failure and continue safely with remaining files.

When running via CLI, errors surface as clean warning messages rather than crashes:


# CLI usage – errors report cleanly, never crash

book-to-skill /tmp/malicious.pdf --mode text

If hidden Unicode characters are removed, you will see:


[security] removed 3 invisible Unicode code point(s) from malicious.pdf

If the work directory is compromised:


ERROR: Refusing to use /tmp/book-to-skill-work: it is a symbolic link, not a real directory.

Summary

  • Invisible-character sanitization in book_to_skill/sanitize.py strips zero-width spaces, bidirectional controls, and other hidden Unicode payloads that could trigger prompt injection in downstream language models.
  • Output-directory hardening via prepare_output_dir() blocks symlinks, enforces UID ownership, and applies 0o700 permissions to prevent data leakage and directory traversal.
  • File-type validation with magic sniffing in extract_single_file() rejects unsupported formats before parsing, blocking malformed archives and format-confusion attacks.
  • Controlled error handling converts all low-level exceptions into ExtractionError instances, preventing information disclosure and ensuring robust pipeline continuity.

Frequently Asked Questions

How does book-to-skill prevent prompt injection attacks hidden in documents?

book-to-skill prevents prompt injection by sanitizing all extracted text through sanitize_extracted_text() in book_to_skill/sanitize.py, which removes invisible Unicode code points including zero-width spaces, bidirectional controls, and annotation characters that attackers use to hide malicious instructions from human reviewers while ensuring language models still process them.

What permissions does book-to-skill set for output directories?

The prepare_output_dir() function forces restrictive 0o700 permissions (owner-only read, write, and execute) on all working directories. On POSIX systems, it also verifies that the directory is owned by the current user ID before proceeding, preventing other users from accessing extracted content.

Yes. The prepare_output_dir() function explicitly checks path.is_symlink() and aborts with an ExtractionError if the target path is a symbolic link. This prevents attackers from redirecting output to sensitive system locations or hijacking the extraction directory to overwrite critical files.

What happens when book-to-skill encounters an unsupported file format?

In extract_single_file(), the system first validates extensions against SUPPORTED_EXTENSIONS. If the extension is unknown, it performs magic sniffing on the first bytes to detect PDF or ZIP structures. Any unsupported format triggers an immediate ExtractionError, ensuring the file never reaches the parser and protecting against format confusion and archive-bomb attacks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →