DEV Community

IronSoftware
IronSoftware

Posted on

pdfminer.six: What the Reading Half of a Pipeline Costs

CVE-2025-64512, GHSA-wf5f-4jwr-ppcp, CVSS 8.6. CMapDB._load_data() deserialises CMap cache files with Python's pickle.loads(), so a crafted PDF could point that loader at an attacker-controlled .pickle.gz file and execute code while the document was being read. Patched in release 20251107. Three weeks later came CVE-2025-70559, GHSA-f83h-ghpp-7wcc, CVSS 7.8, whose advisory states that the patch introduced in the earlier commit does not address the vulnerability reported there, describing a local privilege-escalation path through the same pickle mechanism and closing it in 20251230.

Both are arbitrary-code-execution findings rather than availability issues, so nothing parsing outside documents can run below 20251230. That obligation sits on the reading half of a pipeline whose other half, turning markup into a finished PDF, is a separate dependency with its own release cadence, advisory feed, and upgrade schedule.

The library reads documents and never writes one, so the seam between the halves is not a gap in the library, it is a second entry on the architecture diagram, and the upgrade it implies belongs on a schedule rather than at the next convenient sprint.

Full disclosure. We build IronPDF at Iron Software. This read follows what pdfminer.six's missing write path and pickle advisories add to a pipeline, and what IronPDF produces from markup in a single call.

What Does That Upgrade Obligation Ask of a Schedule?

The advisory work belongs in the same review as the document security and metadata settings on whatever produces the output, and the release rhythm makes it harder to plan around than a monthly cadence would. Releases arrive in bursts rather than a steady drip, with four landing inside four days in late December 2025, preceded by single releases in November 2025, May 2025, and April 2025, so roughly five or six a year. The most recent commit visible on the repository is dated 13 March 2026, with no release following it as of late August 2026. Version numbers are date-based rather than semantic, which means a pin communicates when a build was cut rather than what changed in it. Supported Python runs 3.10 through 3.14 per the published classifiers, and pure Python does not mean dependency-light here, since pdfminer.six requires charset-normalizer and cryptography at runtime, with Pillow as an optional extra for image handling.

How Far Does Character-Level Extraction Go?

Layout analysis is the hard part, and pdfminer.six documents it rather than dodging it, stating that a PDF file does not contain anything resembling paragraphs, sentences, or even words, and the library's whole job is regrouping characters back into words, lines, and boxes from their positions on the page. The object model follows that directly, with LTChar objects each carrying a bounding box, font name, and size, rolling up into LTTextLine, then LTTextBox, then LTPage. On top of that sit four output formats from the same parse, covering plain text, HTML, XML, and hOCR, plus AcroForm field extraction, table-of-contents resolution, and CJK and vertical-writing text. The lineage matters as much as the feature list, because pdfplumber and other extraction libraries build on that layout engine rather than writing their own, which forces the object model to stay stable release over release. For a workflow whose entire job is understanding what sits on a page and precisely where, that model is the ceiling of what an extraction library needs to reach, and IronPDF answers the producing half of the same pipeline from one package.

Reaching the coordinates means walking the tree.

from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer, LTChar

for page_layout in extract_pages("invoice.pdf"):
    for element in page_layout:
        if isinstance(element, LTTextContainer):
            for text_line in element:
                for character in text_line:
                    if isinstance(character, LTChar):
                        print(character.get_text(), character.bbox, character.fontname, character.size)
Enter fullscreen mode Exit fullscreen mode

That prints one line per character, each with its bounding box, font name, and point size, which is the level of detail a field-location or signature-block problem actually needs. The equivalent starting point on the producing side is pulling the text back out of a document that a renderer has just written.

Where the Boundary Sits

Every converter produces something other than a PDF. The four options pdfminer.six offers are plain text, HTML, XML, and hOCR, and not one of them writes a document back out, so a workflow that has to return an actual PDF, even one that merely restates content it just extracted, brings a second library in to write and assemble it.

Grouping accuracy is a tuning job rather than a default. Characters become lines and boxes through the LAParams class, and the documentation states that the output of the layout analysis heavily depends on those parameters. A multi-column layout or a dense table is not guaranteed to group correctly on the first attempt, and it groups correctly once somebody tunes LAParams against that specific document shape, which is per-shape work that arrives with every new supplier format.

A page with no text layer gives the parser nothing to work with. What pdfminer.six reads is character and font objects already encoded in the file, so a scanned page saved as an image returns nothing until an OCR pass puts a text layer there, and that OCR pass is another tool again. By that point the diagram carries three dependencies for a job that started as one, which is the part worth pricing before rasterising or re-rendering the page gets added on top.

The whole split sits in one table.

Task pdfminer.six 20260107 IronPDF for Python
Text out of an existing PDF Character coordinates through LTChar and LTTextBox ExtractAllText on a loaded PdfDocument
Producing a PDF file No write path in any converter RenderHtmlAsPdf and SaveAs, through Chromium
Form fields AcroForm field extraction Reading and filling fields on the same document
Output formats from one pass Text, HTML, XML, and hOCR PDF, plus page images through rasterising
Runtime dependencies charset-normalizer and cryptography required 1 package, 0 system dependencies
Licence MIT License Commercial, one tier
Python versions accepted 3.10 to 3.14 3.7 and later
Current release 20260107, date-based, 5 or 6 a year 2026.9.0.2 on 1 September 2026, 11 in 12 months

Table 1. Where each library operates in a document pipeline, pdfminer.six against IronPDF for Python, as each project documents itself.

The second row is the seam, since turning markup into the finished document is the half with no entry point here at all, and everything else in the table is what the reading half brings with it.

Where IronPDF Picks Up

The producing half is a three-line job when the input is markup rather than a parsed page.

from ironpdf import *

renderer = ChromePdfRenderer()
pdf = renderer.RenderHtmlAsPdf("<h1>Extracted Summary</h1><p>Generated from parsed invoice data.</p>")
pdf.SaveAs("summary-report.pdf")
Enter fullscreen mode Exit fullscreen mode

That writes a finished PDF from the markup, rendered through Chromium so the stylesheet behaves as it does in a browser. Static template files and live URLs go through the same object via RenderHtmlFileAsPdf and RenderUrlAsPdf, so the entry point stays the same wherever the markup lives, and the Python API reference covers the surface beyond rendering.

IronPDF produces the document the pipeline has to hand back, from markup, in a single call, which keeps the outbound half off a second dependency with its own advisory feed and its own upgrade schedule. The narrow case that stays with pdfminer.six is character-level extraction where coordinates and font data are the deliverable. IronPDF has a free trial if you want to test the generation half against your own template while the extraction step keeps running as it is.

What sits on the other end of your pipeline once the extraction finishes, a template renderer, a second PDF library, or a step still held together by hand? Tell us in the comments, because that seam is where most of these architectures actually get decided.

pdfminer.six is the property of its maintainers, and we have no affiliation with the project. The release, dependency, and advisory details above are drawn from the project's own repository, PyPI metadata, and the published CVE records at the time of writing. If a detail has moved since, correct us in the comments.

Top comments (0)