CVE-2026-54071
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
BabelDOC is a document translation tool. Prior to 0.6.3, BabelDOC's vendored PDF parser in babeldoc/pdfminer/cmapdb.py deserializes untrusted pickle data when CMapDB._load_data() loads CMap files. PDF-controlled Encoding or CMapName values and embedded PostScript usecmap operators can reach this sink after path separators are decoded, while _normalize_cmap_name() removes only a leading slash. Absolute paths or traversal sequences can escape the trusted CMap directories through os.path.join(), select an attacker-writable .pickle.gz file, and cause pickle.loads() to execute arbitrary Python code with the privileges of the BabelDOC process. This issue is fixed in version 0.6.3.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Required
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
7.8
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
BabelDOC is a document translation tool. Prior to 0.6.3, BabelDOC's vendored PDF parser in babeldoc/pdfminer/cmapdb.py deserializes untrusted pickle data when CMapDB._load_data() loads CMap files. PDF-controlled Encoding or CMapName values and embedded PostScript usecmap operators can reach this sink after path separators are decoded, while _normalize_cmap_name() removes only a leading slash. Absolute paths or traversal sequences can escape the trusted CMap directories through os.path.join(), select an attacker-writable .pickle.gz file, and cause pickle.loads() to execute arbitrary Python code with the privileges of the BabelDOC process. This issue is fixed in version 0.6.3.
- Record
CVE-2026-54071- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
EUVDEUVD-2026-64058
7.8
BabelDOC is a document translation tool. Prior to 0.6.3, BabelDOC's vendored PDF parser in babeldoc/pdfminer/cmapdb.py deserializes untrusted pickle data when CMapDB._load_data() loads CMap files. PDF-controlled Encoding or CMapName values and embedded PostScript usecmap operators can reach this sink after path separators are decoded, while _normalize_cmap_name() removes only a leading slash. Absolute paths or traversal sequences can escape the trusted CMap directories through os.path.join(), select an attacker-writable .pickle.gz file, and cause pickle.loads() to execute arbitrary Python code with the privileges of the BabelDOC process. This issue is fixed in version 0.6.3.
- Record
EUVD-2026-64058- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-m8gf-v64p-gfmg
7.8
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Summary
BabelDOC's vendored PDF parser (babeldoc/pdfminer/cmapdb.py) deserializes untrusted pickle data when loading CMap files. The _load_data() method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to os.path.join() and pickle.loads(). Because Python's os.path.join() discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF's /Encoding name (e.g., /#2Ftmp#2Fattacker#2Fevil) can redirect deserialization to any attacker-writable .pickle.gz file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.
Details
The vulnerable function is CMapDB._load_data() at babeldoc/pdfminer/cmapdb.py:232–245:
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # line 233 — only NUL is stripped
filename = "%s.pickle.gz" % name # line 234 — attacker-controlled string
...
for directory in cmap_paths:
path = os.path.join(directory, filename) # line 241 — no realpath/canonical check
if os.path.exists(path):
gzfile = gzip.open(path)
try:
return type(str(name), (), pickle.loads(gzfile.read())) # line 245 — unconditional pickle
Path injection via PDF name hex-encoding. The PDF specification allows name objects to encode arbitrary bytes as #xx. The pdfminer literal-name parser (psparser._parse_literal_hex) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal /#2Ftmp#2Fattacker#2Fevil is decoded to the Python string /tmp/attacker/evil.
Python os.path.join() absolute-path override. When the decoded name starts with / (i.e., it is an absolute path), Python's os.path.join(directory, name + ".pickle.gz") ignores directory entirely and returns the absolute path unchanged. The trusted cmap_paths directories (/usr/share/pdfminer/, the package's own cmap/ folder) are therefore completely bypassed.
Data flow from PDF to sink:
babeldoc/main.py:611–622— CLI accepts a PDF path; only existence and.pdfsuffix are checked.babeldoc/main.py:678–679— path stored inTranslationConfig(input_file=file).babeldoc/format/pdf/high_level.py:472–488—translation_config.input_fileenters the translate pipeline.babeldoc/format/pdf/high_level.py:805–848— PDF saved totemp_pdf_pathand parsed withparse_prepared_pdf_with_new_parser_to_legacy_ir.babeldoc/format/pdf/new_parser/native_parse.py:60–70— prepared pages loaded and interpreted.babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25–34— PyMuPDF opens the PDF and builds page resources.babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84–94— font resources converted toPreparedFontSpec.babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21–35— page resource bundle resolves root font map.babeldoc/format/pdf/new_parser/active_font_runtime.py:79–87— each font spec projected and passed tofont_factory.create_font.babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291–292, 491–493— CID fonts callbuild_cid_cmap(spec, literal_name=literal_name).babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52–77— PDF-controlled/Encoding/CMapNamenormalized and passed toCMapDB.get_cmap._normalize_cmap_name()removes only a single leading/; all other path characters pass through.babeldoc/pdfminer/cmapdb.py:233–245— sink: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized withpickle.loads().
Sanitization gaps:
name.replace("\0", "")…
- Record
GHSA-m8gf-v64p-gfmg- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
NVD
7.8
BabelDOC is a document translation tool. Prior to 0.6.3, BabelDOC's vendored PDF parser in babeldoc/pdfminer/cmapdb.py deserializes untrusted pickle data when CMapDB._load_data() loads CMap files. PDF-controlled Encoding or CMapName values and embedded PostScript usecmap operators can reach this sink after path separators are decoded, while _normalize_cmap_name() removes only a leading slash. Absolute paths or traversal sequences can escape the trusted CMap directories through os.path.join(), select an attacker-writable .pickle.gz file, and cause pickle.loads() to execute arbitrary Python code with the privileges of the BabelDOC process. This issue is fixed in version 0.6.3.
- Record
CVE-2026-54071- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSV
7.8
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
BabelDOC is a document translation tool. Prior to 0.6.3, BabelDOC's vendored PDF parser in babeldoc/pdfminer/cmapdb.py deserializes untrusted pickle data when CMapDB._load_data() loads CMap files. PDF-controlled Encoding or CMapName values and embedded PostScript usecmap operators can reach this sink after path separators are decoded, while _normalize_cmap_name() removes only a leading slash. Absolute paths or traversal sequences can escape the trusted CMap directories through os.path.join(), select an attacker-writable .pickle.gz file, and cause pickle.loads() to execute arbitrary Python code with the privileges of the BabelDOC process. This issue is fixed in version 0.6.3.
- Record
CVE-2026-54071- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVGHSA-m8gf-v64p-gfmg
7.8
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Summary
BabelDOC's vendored PDF parser (babeldoc/pdfminer/cmapdb.py) deserializes untrusted pickle data when loading CMap files. The _load_data() method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to os.path.join() and pickle.loads(). Because Python's os.path.join() discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF's /Encoding name (e.g., /#2Ftmp#2Fattacker#2Fevil) can redirect deserialization to any attacker-writable .pickle.gz file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.
Details
The vulnerable function is CMapDB._load_data() at babeldoc/pdfminer/cmapdb.py:232–245:
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # line 233 — only NUL is stripped
filename = "%s.pickle.gz" % name # line 234 — attacker-controlled string
...
for directory in cmap_paths:
path = os.path.join(directory, filename) # line 241 — no realpath/canonical check
if os.path.exists(path):
gzfile = gzip.open(path)
try:
return type(str(name), (), pickle.loads(gzfile.read())) # line 245 — unconditional pickle
Path injection via PDF name hex-encoding. The PDF specification allows name objects to encode arbitrary bytes as #xx. The pdfminer literal-name parser (psparser._parse_literal_hex) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal /#2Ftmp#2Fattacker#2Fevil is decoded to the Python string /tmp/attacker/evil.
Python os.path.join() absolute-path override. When the decoded name starts with / (i.e., it is an absolute path), Python's os.path.join(directory, name + ".pickle.gz") ignores directory entirely and returns the absolute path unchanged. The trusted cmap_paths directories (/usr/share/pdfminer/, the package's own cmap/ folder) are therefore completely bypassed.
Data flow from PDF to sink:
babeldoc/main.py:611–622— CLI accepts a PDF path; only existence and.pdfsuffix are checked.babeldoc/main.py:678–679— path stored inTranslationConfig(input_file=file).babeldoc/format/pdf/high_level.py:472–488—translation_config.input_fileenters the translate pipeline.babeldoc/format/pdf/high_level.py:805–848— PDF saved totemp_pdf_pathand parsed withparse_prepared_pdf_with_new_parser_to_legacy_ir.babeldoc/format/pdf/new_parser/native_parse.py:60–70— prepared pages loaded and interpreted.babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25–34— PyMuPDF opens the PDF and builds page resources.babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84–94— font resources converted toPreparedFontSpec.babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21–35— page resource bundle resolves root font map.babeldoc/format/pdf/new_parser/active_font_runtime.py:79–87— each font spec projected and passed tofont_factory.create_font.babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291–292, 491–493— CID fonts callbuild_cid_cmap(spec, literal_name=literal_name).babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52–77— PDF-controlled/Encoding/CMapNamenormalized and passed toCMapDB.get_cmap._normalize_cmap_name()removes only a single leading/; all other path characters pass through.babeldoc/pdfminer/cmapdb.py:233–245— sink: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized withpickle.loads().
Sanitization gaps:
name.replace("\0", "")…
- Record
GHSA-m8gf-v64p-gfmg- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVPYSEC-2026-2389
7.8
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Summary
BabelDOC's vendored PDF parser (babeldoc/pdfminer/cmapdb.py) deserializes untrusted pickle data when loading CMap files. The _load_data() method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to os.path.join() and pickle.loads(). Because Python's os.path.join() discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF's /Encoding name (e.g., /#2Ftmp#2Fattacker#2Fevil) can redirect deserialization to any attacker-writable .pickle.gz file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.
Details
The vulnerable function is CMapDB._load_data() at babeldoc/pdfminer/cmapdb.py:232–245:
@classmethod
def _load_data(cls, name: str) -> Any:
name = name.replace("\0", "") # line 233 — only NUL is stripped
filename = "%s.pickle.gz" % name # line 234 — attacker-controlled string
...
for directory in cmap_paths:
path = os.path.join(directory, filename) # line 241 — no realpath/canonical check
if os.path.exists(path):
gzfile = gzip.open(path)
try:
return type(str(name), (), pickle.loads(gzfile.read())) # line 245 — unconditional pickle
Path injection via PDF name hex-encoding. The PDF specification allows name objects to encode arbitrary bytes as #xx. The pdfminer literal-name parser (psparser._parse_literal_hex) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal /#2Ftmp#2Fattacker#2Fevil is decoded to the Python string /tmp/attacker/evil.
Python os.path.join() absolute-path override. When the decoded name starts with / (i.e., it is an absolute path), Python's os.path.join(directory, name + ".pickle.gz") ignores directory entirely and returns the absolute path unchanged. The trusted cmap_paths directories (/usr/share/pdfminer/, the package's own cmap/ folder) are therefore completely bypassed.
Data flow from PDF to sink:
babeldoc/main.py:611–622— CLI accepts a PDF path; only existence and.pdfsuffix are checked.babeldoc/main.py:678–679— path stored inTranslationConfig(input_file=file).babeldoc/format/pdf/high_level.py:472–488—translation_config.input_fileenters the translate pipeline.babeldoc/format/pdf/high_level.py:805–848— PDF saved totemp_pdf_pathand parsed withparse_prepared_pdf_with_new_parser_to_legacy_ir.babeldoc/format/pdf/new_parser/native_parse.py:60–70— prepared pages loaded and interpreted.babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25–34— PyMuPDF opens the PDF and builds page resources.babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84–94— font resources converted toPreparedFontSpec.babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21–35— page resource bundle resolves root font map.babeldoc/format/pdf/new_parser/active_font_runtime.py:79–87— each font spec projected and passed tofont_factory.create_font.babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291–292, 491–493— CID fonts callbuild_cid_cmap(spec, literal_name=literal_name).babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52–77— PDF-controlled/Encoding/CMapNamenormalized and passed toCMapDB.get_cmap._normalize_cmap_name()removes only a single leading/; all other path characters pass through.babeldoc/pdfminer/cmapdb.py:233–245— sink: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized withpickle.loads().
Sanitization gaps:
name.replace("\0", "")…
- Record
PYSEC-2026-2389- CVSS 3.1
- 7.8 High
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
babeldocPyPI |
through 0.6.2 | 0.6.3 |
babeldocPyPI |
before 0.6.3, plus listed versions 0.1.100.1.110.1.120.1.130.1.14 Show 45 moreShow less0.1.150.1.160.1.170.1.180.1.190.1.200.1.210.1.220.1.230.1.240.1.250.1.260.1.270.1.280.1.290.1.300.1.310.1.320.1.60.1.6rc00.1.70.1.80.1.90.2.00.2.10.2.100.2.110.2.120.2.130.2.140.2.150.2.160.2.170.2.180.2.190.2.20.2.200.2.210.2.220.2.230.2.240.2.250.2.260.2.270.2.28 |
0.6.3 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-54071 is a high vulnerability (CVSS 7.8) in Funstory-ai Babeldoc, published July 10, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.38%, the 32nd percentile. Fixed in babeldoc 0.6.3.
References
github.com/funstory-ai/BabelDOC/security/advisories/GHSA-m8gf-v64p-gfmg
github.com/funstory-ai/BabelDOC/commit/28f784ca6b437dbba040bfd9c67110373cd0924b
github.com/funstory-ai/BabelDOC/blob/main/docs/release-notes/v0.6.3.md
github.com/funstory-ai/BabelDOC/releases/tag/v0.6.3
github.com/advisories/GHSA-m8gf-v64p-gfmg
github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54071.json
nvd.nist.gov/vuln/detail/CVE-2026-54071
github.com/funstory-ai/BabelDOC
pypi.org/project/babeldoc
Left out here: 134 versions and the rest of 3 source descriptions. The JSON record has them.