# How book-to-skill Protects Against Document-Borne Prompt Injection: A Multi-Layer Security Analysis

> Discover how book-to-skill safeguards against document-borne prompt injection. Learn about its multi-layer security pipeline, Unicode stripping, and XXE attack prevention for robust LLM protection.

- Repository: [Virgilio Junior/book-to-skill](https://github.com/virgiliojr94/book-to-skill)
- Tags: deep-dive
- Published: 2026-08-28

---

**book-to-skill treats every input document as untrusted data and applies a multi-layer hardening pipeline that strips invisible Unicode constructs, blocks XXE attacks, and verifies generated outputs before they reach the language model.**

Document-borne prompt injection represents a critical attack vector where malicious actors embed invisible instructions within uploaded files to manipulate AI behavior. The open-source `book-to-skill` repository implements comprehensive defenses against document-borne prompt injection through systematic sanitization of every extracted text fragment. This article examines the specific security mechanisms implemented in the codebase, from low-level Unicode filtering to high-level architectural safeguards.

## The Four-Layer Security Architecture

The repository employs a defense-in-depth strategy that hardens every stage of the document processing pipeline. Each layer targets specific attack vectors commonly exploited in document-borne prompt injection scenarios.

### Extraction Sanitization Layer (sanitize.py)

The primary defense lives in [`book_to_skill/sanitize.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/sanitize.py), which defines a master set of invisible code points (`_INVISIBLE_CODEPOINTS`) known to facilitate hidden payload attacks. This set includes:

- **Zero-width characters** such as U+200B (ZERO WIDTH SPACE) and U+FEFF (ZERO WIDTH NO-BREAK SPACE)
- **Bidirectional control codes** like U+202E (RIGHT-TO-LEFT OVERRIDE) that reorder text for humans while models read the original sequence
- **Unicode tag block** (U+E0000–U+E007F) used to smuggle ASCII payloads as invisible tags
- **Variation selectors, musical formatting ranges, and invisible letters**

The public helper `sanitize_extracted_text(text)` scans every character of raw extracted text, discarding any code point present in the invisible set. It returns the cleaned text alongside a count of removed characters. If sanitization leaves a document with no visible content, the function triggers an `ExtractionError`, terminating processing before any hidden payload reaches downstream components.

### Parser-Level Security Guards

Before text extraction even begins, input-specific parsers implement strict security controls:

- **DOCX hardening**: [`book_to_skill/parsers/docx.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/docx.py) rejects any XML part declaring a DTD or external entities, preventing XML External Entity (XXE) attacks that could expose local files or internal network resources
- **Argument sanitization**: [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) validates that all external tool invocations (such as `pdftotext` or `ebook-convert`) receive absolute, validated paths, ensuring malicious filenames cannot be interpreted as command-line flags

### Post-Generation Verification

After [`SKILL.md`](https://github.com/virgiliojr94/book-to-skill/blob/main/SKILL.md) generation completes, [`tools/scan_generated_skill.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tools/scan_generated_skill.py) performs an advisory scan for residual invisible Unicode, model-control tags, instruction-override phrases, and other injection patterns. Unlike the extraction sanitizer which blocks processing, this tool reports findings by rule and file location without exposing the offending text, allowing security teams to audit generated outputs.

### Continuous Security Testing

The CI pipeline enforces code security through automated analysis tools including CodeQL, Bandit (high-severity checks), Zizmor, and CVE audits on every pull request. This ensures the defensive mechanisms themselves remain free of known vulnerabilities.

## Core Sanitization Implementation

The extraction sanitization logic centralizes in [`book_to_skill/sanitize.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/sanitize.py), where the `sanitize_extracted_text` function serves as the gatekeeper for all document content. The implementation walks the input string character-by-character, checking membership against the pre-defined invisible code point set.

In [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py), every parser invokes this function immediately after text extraction:

```python
text, removed_invisible = sanitize_extracted_text(text)
if removed_invisible:
    print(
        f"  [security] removed {removed_invisible} invisible Unicode "
        f"code point(s) from {input_path.name}",
        file=sys.stderr,
    )

```

This centralized invocation ensures consistent protection across PDF, DOCX, EPUB, and other supported formats.

## Practical Usage Examples

You can observe the document-borne prompt injection defenses in action through both the command-line interface and direct Python API usage.

### Automatic Sanitization via CLI

When processing documents through the main entry point, security logging appears automatically:

```bash
$ book-to-skill my_book.pdf

# During extraction you will see a line like:

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

```

### Direct Python API Access

Import the sanitizer directly for custom workflows:

```python
from book_to_skill.sanitize import sanitize_extracted_text

raw = "Hello\u200B\u200CWorld"          # contains ZERO WIDTH SPACE & NON-JOINER

clean, removed = sanitize_extracted_text(raw)

print(clean)      # → "HelloWorld"

print(removed)    # → 2

```

### Scanning Generated Skills

Verify that generated skill files contain no residual injection vectors:

```bash
$ python -m tools.scan_generated_skill path/to/generated_skill

# Reports any residual invisible Unicode or model-control tags found.

```

## Summary

- **book_to_skill/sanitize.py** implements the primary defense by stripping zero-width spaces, bidirectional overrides, Unicode tag blocks, and other invisible characters from all extracted text
- **Parser-level guards** in [`book_to_skill/parsers/docx.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/docx.py) prevent XXE attacks while [`book_to_skill/utils.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/utils.py) sanitizes external tool arguments
- **Post-generation scanning** via [`tools/scan_generated_skill.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/tools/scan_generated_skill.py) provides advisory detection of residual threats in output files
- **Completely invisible documents** trigger an `ExtractionError` and abort processing, preventing entirely hidden payloads from reaching the language model
- **Continuous integration** maintains codebase integrity through CodeQL, Bandit, Zizmor, and CVE auditing

## Frequently Asked Questions

### What specific invisible Unicode characters does book-to-skill remove?

The sanitizer targets zero-width spaces (U+200B, U+FEFF), bidirectional control codes (U+202E), the Unicode tag block range (U+E0000–U+E007F), variation selectors, musical formatting ranges, and various invisible letters. The complete enumeration resides in the `_INVISIBLE_CODEPOINTS` set defined in [`book_to_skill/sanitize.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/sanitize.py).

### How does the tool handle documents containing only invisible characters?

If `sanitize_extracted_text` removes all content from a document, the system raises an `ExtractionError` and terminates processing immediately. This guarantees that a completely hidden payload never proceeds to summarization or skill generation stages.

### What protections exist against XXE attacks in DOCX parsing?

The DOCX parser in [`book_to_skill/parsers/docx.py`](https://github.com/virgiliojr94/book-to-skill/blob/main/book_to_skill/parsers/docx.py) explicitly rejects any XML part that declares a DTD or external entities. This prevents attackers from using malicious DOCX files to read local filesystem contents or make unauthorized network requests during document processing.

### Can the sanitization functions be used independently of the full pipeline?

Yes. You can import `sanitize_extracted_text` directly from `book_to_skill.sanitize` for use in custom extraction workflows. The function requires only a text string input and returns a tuple of `(cleaned_text, removed_count)`, making it suitable for integration into any Python application requiring protection against document-borne prompt injection.