Security Hardening#
Docling has addressed three classes of critical security vulnerabilities across its XML parsing, OCR subprocess execution, and model download subsystems. The fixes were shipped in three separate PRs in early 2026 as part of a coordinated hardening effort.
1. XXE Attacks in XML Parsing Backends#
Threat: XML External Entity (XXE) injection allows an attacker-controlled document to trigger network requests or local file reads during parsing.
Two XML backends were hardened:
-
JATS backend (
jats_backend.py):lxml.etree.XMLParseris now constructed with four explicit flags:resolve_entities=False,load_dtd=False,no_network=True, anddtd_validation=False. The module-level security comment describes the threat model. -
USPTO backend (
uspto_backend.py): USPTO files require DTD declarations, so a customdefusedxml.sax.make_parser()configuration is used in all three parser entry points . External entity resolution is blocked viafeature_external_ges=Falseandfeature_external_pes=False, while DTD declarations are still permitted (forbid_dtd=False). The security analysis in the module header explains why fulldefusedxmldefaults cannot be used for USPTO.
defusedxml (>=0.7.1,<0.8.0) was added as a runtime dependency .
2. Command Injection in Tesseract CLI Execution#
Threat: Unsanitized user-supplied language codes, paths, or filenames passed to subprocess can enable shell metacharacter injection and arbitrary command execution (reported as V-001, critical severity).
tesseract_ocr_cli_model.py was hardened with four sanitization helpers, all called at construction time :
| Helper | What it validates | Lines |
|---|---|---|
_sanitize_lang() | Language identifiers against _VALID_LANG_RE (^[a-zA-Z0-9_/][a-zA-Z0-9_/+-]*$) | 90–102 |
_sanitize_path() | Null-byte rejection + absolute path resolution | 105–112 |
_sanitize_cmd() | Null-byte rejection for the tesseract executable path | 115–122 |
_sanitize_filename() | Null-byte rejection + absolute path resolution for input images | 125–132 |
All subprocess.run() / Popen() calls use shell=False and stdin=DEVNULL . Numeric parameters (e.g., DPI) are coerced with int() to prevent type-confusion injection .
3. Directory Traversal (ZIP Slip) in Model Downloads#
Threat: A malicious ZIP archive in the EasyOCR model download path could use crafted member paths (e.g., ../../etc/passwd) to write files outside the intended extraction directory.
easyocr_model.py replaced zip_ref.extractall() with per-member iteration :
- For each member, compute
os.path.realpath(os.path.join(local_dir, member.filename)). - Verify the resolved path starts with
os.path.realpath(local_dir) + os.sep. - If not, raise
SecurityError— a newBaseErrorsubclass defined indocling/exceptions.py.
This pattern (canonical path prefix check) is the standard defense against ZIP slip .
Key Source Files#
| File | Vulnerability addressed |
|---|---|
docling/backend/xml/jats_backend.py | XXE — lxml parser flags |
docling/backend/xml/uspto_backend.py | XXE — defusedxml SAX parser flags |
docling/models/stages/ocr/tesseract_ocr_cli_model.py | Command injection — input sanitization |
docling/models/stages/ocr/easyocr_model.py | ZIP slip — realpath extraction guard |
docling/exceptions.py | SecurityError exception class |