How book-to-skill Protects Against Document-Borne Prompt Injection: A Multi-Layer Security Analysis
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, 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.pyrejects 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.pyvalidates that all external tool invocations (such aspdftotextorebook-convert) receive absolute, validated paths, ensuring malicious filenames cannot be interpreted as command-line flags
Post-Generation Verification
After SKILL.md generation completes, 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, 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, every parser invokes this function immediately after text extraction:
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:
$ 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:
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:
$ 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.pyprevent XXE attacks whilebook_to_skill/utils.pysanitizes external tool arguments - Post-generation scanning via
tools/scan_generated_skill.pyprovides advisory detection of residual threats in output files - Completely invisible documents trigger an
ExtractionErrorand 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.
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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →