You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The b64 extension inlines images referenced by <img src="..."> as base64 data URIs. When resolving the src path it joins it onto the configured base_path with os.path.normpath and opens the result directly, with no check that the resolved path stays inside base_path. A src containing ../ sequences, or an absolute path, therefore reads a file outside base_path as long as that file has an allowed image extension (.png, .jpg, .jpeg, .gif, .svg). The base64 of that file is then embedded in the rendered output, disclosing its contents.
This is a separate code path from the snippets traversal issues (GHSA-jh85-wwv9-24hv, GHSA-62q4-447f-wv8h). It lives in pymdownx/b64.py and has no path restriction of any kind. Confirmed on 10.21.3 installed from PyPI.
Details
In pymdownx/b64.py, function repl_path (around lines 68 to 90 on main):
ifis_absolute:
file_name=os.path.normpath(path) # absolute src: base_path ignored entirelyelse:
file_name=os.path.normpath(os.path.join(base_path, path)) # relative src: '../' escapes base_pathifos.path.exists(file_name):
ext=os.path.splitext(file_name)[1].lower()
forb64_extinfile_types:
ifextinb64_ext:
withopen(file_name, "rb") asf: # opened with no containment check
...
There is no startswith(base_path), no os.path.realpath comparison, and no rejection of ... Both branches are reachable from an attacker-controlled src.
PoC
Reproduced against an unmodified pymdown-extensions==10.21.3 from PyPI. The script creates a base_path directory and a PNG one level above it, then renders Markdown whose image src points outside base_path, and confirms the outside file's bytes appear base64-encoded in the output.
The base64 of a file outside base_path is present in the output. The absolute-path branch behaves the same way: an absolute src bypasses base_path entirely via os.path.normpath(path). Both were confirmed leaking.
Impact
An application that renders untrusted Markdown with pymdownx.b64 enabled exposes the contents of image-extension files on the server, or any path the process can read, to whoever controls the Markdown and whoever views the output. The reach is bounded by the image-extension check, so it is a targeted file read rather than full arbitrary read, but it still discloses file contents that were never meant to be exposed.
Suggested fix
Resolve the real path and require it to stay within base_path before opening:
file_name=os.path.realpath(os.path.join(base_path, path))
base_real=os.path.realpath(base_path)
iffile_name!=base_realandnotfile_name.startswith(base_real+os.sep):
returnm.group(0) # leave the tag untouched; do not read outside base_path
The same containment check should apply to the absolute-path branch rather than trusting an absolute src. Using realpath instead of abspath also closes the related symlink-following gap in the snippets handler.
BREAK: B64: Restricts relative links to base_path by default. Can be disabled by setting new restrict_path
option to False. The new root_path can be specified if paths are desired to be restricted to a different
location separate base_path which is also used as a relative base for image paths.
NEW: Drop Python 3.9 support.
FIX: Tabbed: Fix issue where an empty title would cause an exception.
Configuration
📅 Schedule: (UTC)
Branch creation
At any time (no schedule defined)
Automerge
At any time (no schedule defined)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
If you want to rebase/retry this PR, check this box
Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>
Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer TIP This summary will be updated as you push new changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dependenciesPull requests that update a dependency file
0 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
10.21.3→11.0PyMdown Extensions: Path traversal in the b64 extension lets
read files outside base_path
CVE-2026-61632 / GHSA-9xwg-3r6f-jcx2
More information
Details
Summary
The
b64extension inlines images referenced by<img src="...">as base64 data URIs. When resolving thesrcpath it joins it onto the configuredbase_pathwithos.path.normpathand opens the result directly, with no check that the resolved path stays insidebase_path. Asrccontaining../sequences, or an absolute path, therefore reads a file outsidebase_pathas long as that file has an allowed image extension (.png,.jpg,.jpeg,.gif,.svg). The base64 of that file is then embedded in the rendered output, disclosing its contents.This is a separate code path from the
snippetstraversal issues (GHSA-jh85-wwv9-24hv, GHSA-62q4-447f-wv8h). It lives inpymdownx/b64.pyand has no path restriction of any kind. Confirmed on10.21.3installed from PyPI.Details
In
pymdownx/b64.py, functionrepl_path(around lines 68 to 90 onmain):There is no
startswith(base_path), noos.path.realpathcomparison, and no rejection of... Both branches are reachable from an attacker-controlledsrc.PoC
Reproduced against an unmodified
pymdown-extensions==10.21.3from PyPI. The script creates abase_pathdirectory and a PNG one level above it, then renders Markdown whose imagesrcpoints outsidebase_path, and confirms the outside file's bytes appear base64-encoded in the output.Output:
The base64 of a file outside
base_pathis present in the output. The absolute-path branch behaves the same way: an absolutesrcbypassesbase_pathentirely viaos.path.normpath(path). Both were confirmed leaking.Impact
An application that renders untrusted Markdown with
pymdownx.b64enabled exposes the contents of image-extension files on the server, or any path the process can read, to whoever controls the Markdown and whoever views the output. The reach is bounded by the image-extension check, so it is a targeted file read rather than full arbitrary read, but it still discloses file contents that were never meant to be exposed.Suggested fix
Resolve the real path and require it to stay within
base_pathbefore opening:The same containment check should apply to the absolute-path branch rather than trusting an absolute
src. Usingrealpathinstead ofabspathalso closes the related symlink-following gap in the snippets handler.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
facelessuser/pymdown-extensions (pymdown-extensions)
v11.0Compare Source
11.0
base_pathby default. Can be disabled by setting newrestrict_pathoption to
False. The newroot_pathcan be specified if paths are desired to be restricted to a differentlocation separate
base_pathwhich is also used as a relative base for image paths.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.