Repository navigation
A security and bug-fix release.
Plugin renderers now escape user content in HTML attribute, label and math positions,
attrs drops event-handler attributes by default and validates URL-valued attributes,
and the regular expressions in texmath ```math blocks and admon headers no longer backtrack polynomially (#143).
It also fixes parser hangs in gfm_autolink (#160) and texmath (#156),
and adds the section_ref plugin.
The release requires markdown-it-py >= 3.0 and supports Python 3.10 to 3.14.
Several fixes change rendered output: see Upgrade notes below.
Upgrade notes
- Requires markdown-it-py >= 3.0 (#170).
The declared floor was 2.0, but 0.5.0 and later already failed to importsubscriptwith markdown-it-py 2.x.
section_ref,gfmandgfm_autolinkstill need markdown-it-py >= 4.1 and raiseRuntimeErroron older versions.
Python 3.10 to 3.14 are supported. attrs_plugin/attrs_block_plugindefaults are stricter (#150).
With noallowedlist, event-handler attributes (any key starting withon, in any case) are removed
and moved totoken.meta["insecure_attrs"];styleis kept.
Whateverallowedsays,href,src,action,formactionandxlink:hrefare removed whenmd.validateLinkrejects their value,
so[a](https://x){href="javascript:alert(1)"}keepshref="https://x".
Text spans ([text]{...}) now honourallowed, which they used to bypass.
This is a baseline, not a sanitiser: for untrusted input, pass an explicit list such asallowed=("id", "class").- Renderers escape their output (#150, #165).
Thetexmathdefault renderer HTML-escapes the math source ($a<b$now renders<eq>a<b</eq>),
and thedollarmathequation label, themyst_blocktarget label, thecolon_fenceclass name
and thetasklistslabel_afterlabel are escaped too.
Benign text containing&,<,>or"in these positions is now emitted as entities. anchors_pluginids can change (#159).
An explicit heading id (such as{#custom}fromattrs_block_plugin) is now kept instead of being overwritten,
and generated slugs avoid it.
The default slug function no longer strips whitespace from the title,
matching MyST-Parser'sgithub_slugify:
a title that starts or ends with a space next to an image, raw HTML or an entity gets a different id
(# Foo now givesfoo-, it wasfoo).
To keep the old ids, passslug_func=lambda title: slugify(title.strip()), withslugifyfrommdit_py_plugins.anchors.index.mdit_py_plugins.attrs.parse.parse()raises on an unterminated attribute group, instead of succeeding at the end of the string
(#153).
Every plugin call site declines the group, so`a`{.anow renders<code>a</code>{.a; it used to drop the{.a.- The
section_reftoken shape is public API (#169): see its entry below.
🔒 Security
Thanks to @KrisKennawayDD for reporting these issues in #143,
and to @KyleKing for the ReDoS analysis and fixes.
-
🔒 FIX: Escape plugin renderer output and harden the
attrsdefaults, thanks to @KyleKing for the cross-plugin XSS test (#150, folding in #151)With markdown-it-py's
htmloption off, untrusted Markdown could still inject HTML or script through:
thetexmathdefault renderer (math source), thedollarmathequation label (idand permalinkhref),
themyst_block(name)=target label, andattrs/attrs_blockevent-handler andjavascript:URL attributes.
All of these are now escaped or filtered (see Upgrade notes),
andtests/test_xss.pyrenders injection probes through each plugin's default renderer. -
🔒 FIX: Escape the
colon_fenceclass name and thetasklistslabel_afterlabel (#165):::a"onmouseover="alert(1)rendered an attribute-breakingclass="block-a"onmouseover="alert(1)",
and- [ ] <img src=x onerror=alert(1)>withlabel=True, label_after=Truerendered a raw<img>. -
🔒 FIX: ReDoS in
texmath```mathblocks andadmonheaders, thanks to @KyleKing (#148)The
texmath```mathpatterns (thegitlabandjuliadelimiters) backtracked cubically
on a fence followed by a long whitespace run with no closing fence,
and theadmonmulti-tag title pattern backtracked quadratically on a header with a long run of spaces or double quotes.
Both now run in linear time and match what they matched before, so output is unchanged.
✨ New
-
✨ NEW: Add the section reference plugin (
section_ref) (#144, #168, #169)The
section_refplugin captures section-sign references, the syntax LLMs
commonly use to point at numbered headings, into dedicatedsection_reftokens,
so downstream renderers (e.g. MyST-Parser) can resolve them to heading cross-references:See §1, §1.1 and §2.3.4 for details.
A
§must be immediately followed by one or more.-separated levels of 1 to 9 ASCII digits, with no spaces.
A trailing.is not part of the reference, and a reference directly followed by an ASCII letter (§1a),
or with a level longer than nine digits, stays literal text.
Each reference is asection_refleaf token whosecontentis the text as written
and whosemeta["numbers"]lists each level as an int (e.g.[1, 1]for§1.1).
The grammar and the token shape are documented in the plugin docstring and are public API from this release.
It renders as<span class="section-ref">§1.1</span>by default.Requires markdown-it-py >= 4.1.0.
🐛 Fixes
-
🐛 FIX:
gfm_autolinkno longer hangs on autolink-like text in link labels (#160), thanks to @AryanRG (#161) -
🐛 FIX:
texmathno longer loops forever on block math opened in a blockquote and closed after it (#156), thanks to @acaprino (#157) -
🐛 FIX:
dollarmath: a same-line$$…$$followed by text no longer swallows the following lines (#147), thanks to @KyleKing (#149) -
🐛 FIX:
dollarmath: multi-line$$blocks in a blockquote no longer keep the>prefixes in their content (#125), thanks to @BetterAndBetterII (#152) -
🐛 FIX:
front_matter: parse front matter closed by the YAML...document-end marker, thanks to @feiiiiii5 (#158)A block closed by
...used to leak into the document as text when the...was the document's last line or had trailing spaces,
and otherwise kept the...intoken.content.
The marker must be at column 0 and may be followed by spaces only. -
🐛 FIX:
footnote: reserve an inline footnote's id before parsing its content, thanks to @civilwarinfrance (#162)A
[^named]reference or a nested^[...]inside an inline footnote no longer loses the inner note, duplicates an id, or raisesKeyError: 'count'. -
🐛 FIX:
anchors: keep explicit heading ids, and do not strip slug titles, thanks to @muneikh (#159) -
🐛 FIX:
attrs: unterminated groups, class accumulation and closing-token merge (#153)An unterminated attribute group no longer swallows the text after it.
Classes from adjacent attribute groups are combined on spans and links ([a]{.x}{.y}givesclass="x y"),
as they already were on inline code and images.
An attributes block at the end of a container is no longer merged onto the closing token
(> {.a}rendered</blockquote class="a">). -
🐛 FIX:
myst_roleno longer rejects a role at the start of inline content when the content ends with a backslash,
and\\{name}`x`(an escaped backslash before a role) now parses the role (#62) (#145)
📚 Documentation
- 📚 DOCS: Explain front matter extraction and HTML rendering (#133), thanks to @Gonghan-Princess (#154)
- 📚 DOCS: A runnable usage example (#44), the
dollarmathrendering contract and a "Rendering dollarmath output" guide (#111),
and Read the Docs builds on Python 3.13 (#166)
🔧 Maintenance
- 🔧 Require markdown-it-py >= 3.0, test the floor and Python 3.14 in CI, and update the classifiers (#170)
- 🔧 Releases are published to PyPI with trusted publishing (#141), with attestations (#142),
from distributions built withpython -m buildand checked on every pull request (#164) - 🔧 pre-commit autoupdate (ruff 0.17, mypy 2.4) with a pinned ruff base rule set (#138),
and a non-blocking Codecov upload (#167)
Full Changelog: v0.6.1...v0.7.0
Full changelog: v0.6.1...v0.7.0