|
1 | 1 | # Change Log |
2 | 2 |
|
3 | | -## 0.7.0 - 2026-07-19 |
4 | | - |
5 | | -- β¨ NEW: Add section reference plugin (`section_ref`) (#144) |
6 | | - |
7 | | - The `section_ref` plugin captures section-sign references β the syntax LLMs |
8 | | - commonly use to point at numbered headings β into dedicated `section_ref` tokens, |
| 3 | +## 0.7.0 - 2026-11-10 |
| 4 | + |
| 5 | +A security and bug-fix release. |
| 6 | +Plugin renderers now escape user content in HTML attribute, label and math positions, |
| 7 | +`attrs` drops event-handler attributes by default and validates URL-valued attributes, |
| 8 | +and the regular expressions in `texmath` ```` ```math ```` blocks and `admon` headers no longer backtrack polynomially (#143). |
| 9 | +It also fixes parser hangs in `gfm_autolink` (#160) and `texmath` (#156), |
| 10 | +and adds the `section_ref` plugin. |
| 11 | +The release requires markdown-it-py >= 3.0 and supports Python 3.10 to 3.14. |
| 12 | +Several fixes change rendered output: see **Upgrade notes** below. |
| 13 | + |
| 14 | +### Upgrade notes |
| 15 | + |
| 16 | +- **Requires markdown-it-py >= 3.0** ([#170](https://github.com/executablebooks/mdit-py-plugins/pull/170)). |
| 17 | + The declared floor was 2.0, but 0.5.0 and later already failed to import `subscript` with markdown-it-py 2.x. |
| 18 | + `section_ref`, `gfm` and `gfm_autolink` still need markdown-it-py >= 4.1 and raise `RuntimeError` on older versions. |
| 19 | + Python 3.10 to 3.14 are supported. |
| 20 | +- **`attrs_plugin` / `attrs_block_plugin` defaults are stricter** ([#150](https://github.com/executablebooks/mdit-py-plugins/pull/150)). |
| 21 | + With no `allowed` list, event-handler attributes (any key starting with `on`, in any case) are removed |
| 22 | + and moved to `token.meta["insecure_attrs"]`; `style` is kept. |
| 23 | + Whatever `allowed` says, `href`, `src`, `action`, `formaction` and `xlink:href` are removed when `md.validateLink` rejects their value, |
| 24 | + so `[a](https://x){href="javascript:alert(1)"}` keeps `href="https://x"`. |
| 25 | + Text spans (`[text]{...}`) now honour `allowed`, which they used to bypass. |
| 26 | + This is a baseline, not a sanitiser: for untrusted input, pass an explicit list such as `allowed=("id", "class")`. |
| 27 | +- **Renderers escape their output** ([#150](https://github.com/executablebooks/mdit-py-plugins/pull/150), [#165](https://github.com/executablebooks/mdit-py-plugins/pull/165)). |
| 28 | + The `texmath` default renderer HTML-escapes the math source (`$a<b$` now renders `<eq>a<b</eq>`), |
| 29 | + and the `dollarmath` equation label, the `myst_block` target label, the `colon_fence` class name |
| 30 | + and the `tasklists` `label_after` label are escaped too. |
| 31 | + Benign text containing `&`, `<`, `>` or `"` in these positions is now emitted as entities. |
| 32 | +- **`anchors_plugin` ids can change** ([#159](https://github.com/executablebooks/mdit-py-plugins/pull/159)). |
| 33 | + An explicit heading id (such as `{#custom}` from `attrs_block_plugin`) is now kept instead of being overwritten, |
| 34 | + and generated slugs avoid it. |
| 35 | + The default slug function no longer strips whitespace from the title, |
| 36 | + matching MyST-Parser's `github_slugify`: |
| 37 | + a title that starts or ends with a space next to an image, raw HTML or an entity gets a different id |
| 38 | + (`# Foo ` now gives `foo-`, it was `foo`). |
| 39 | + To keep the old ids, pass `slug_func=lambda title: slugify(title.strip())`, with `slugify` from `mdit_py_plugins.anchors.index`. |
| 40 | +- **`mdit_py_plugins.attrs.parse.parse()` raises on an unterminated attribute group**, instead of succeeding at the end of the string |
| 41 | + ([#153](https://github.com/executablebooks/mdit-py-plugins/pull/153)). |
| 42 | + Every plugin call site declines the group, so `` `a`{.a `` now renders `<code>a</code>{.a`; it used to drop the `{.a`. |
| 43 | +- **The `section_ref` token shape is public API** ([#169](https://github.com/executablebooks/mdit-py-plugins/pull/169)): see its entry below. |
| 44 | + |
| 45 | +### π Security |
| 46 | + |
| 47 | +Thanks to [@KrisKennawayDD](https://github.com/KrisKennawayDD) for reporting these issues in #143, |
| 48 | +and to [@KyleKing](https://github.com/KyleKing) for the ReDoS analysis and fixes. |
| 49 | + |
| 50 | +- π FIX: Escape plugin renderer output and harden the `attrs` defaults, thanks to [@KyleKing](https://github.com/KyleKing) for the cross-plugin XSS test ([#150](https://github.com/executablebooks/mdit-py-plugins/pull/150), folding in [#151](https://github.com/executablebooks/mdit-py-plugins/pull/151)) |
| 51 | + |
| 52 | + With markdown-it-py's `html` option off, untrusted Markdown could still inject HTML or script through: |
| 53 | + the `texmath` default renderer (math source), the `dollarmath` equation label (`id` and permalink `href`), |
| 54 | + the `myst_block` `(name)=` target label, and `attrs` / `attrs_block` event-handler and `javascript:` URL attributes. |
| 55 | + All of these are now escaped or filtered (see **Upgrade notes**), |
| 56 | + and `tests/test_xss.py` renders injection probes through each plugin's default renderer. |
| 57 | + |
| 58 | +- π FIX: Escape the `colon_fence` class name and the `tasklists` `label_after` label ([#165](https://github.com/executablebooks/mdit-py-plugins/pull/165)) |
| 59 | + |
| 60 | + `:::a"onmouseover="alert(1)` rendered an attribute-breaking `class="block-a"onmouseover="alert(1)"`, |
| 61 | + and `- [ ] <img src=x onerror=alert(1)>` with `label=True, label_after=True` rendered a raw `<img>`. |
| 62 | + |
| 63 | +- π FIX: ReDoS in `texmath` ```` ```math ```` blocks and `admon` headers, thanks to [@KyleKing](https://github.com/KyleKing) ([#148](https://github.com/executablebooks/mdit-py-plugins/pull/148)) |
| 64 | + |
| 65 | + The `texmath` ```` ```math ```` patterns (the `gitlab` and `julia` delimiters) backtracked cubically |
| 66 | + on a fence followed by a long whitespace run with no closing fence, |
| 67 | + and the `admon` multi-tag title pattern backtracked quadratically on a header with a long run of spaces or double quotes. |
| 68 | + Both now run in linear time and match what they matched before, so output is unchanged. |
| 69 | + |
| 70 | +### β¨ New |
| 71 | + |
| 72 | +- β¨ NEW: Add the section reference plugin (`section_ref`) ([#144](https://github.com/executablebooks/mdit-py-plugins/pull/144), [#168](https://github.com/executablebooks/mdit-py-plugins/pull/168), [#169](https://github.com/executablebooks/mdit-py-plugins/pull/169)) |
| 73 | + |
| 74 | + The `section_ref` plugin captures section-sign references, the syntax LLMs |
| 75 | + commonly use to point at numbered headings, into dedicated `section_ref` tokens, |
9 | 76 | so downstream renderers (e.g. MyST-Parser) can resolve them to heading cross-references: |
10 | 77 |
|
11 | 78 | ```markdown |
12 | 79 | See Β§1, Β§1.1 and Β§2.3.4 for details. |
13 | 80 | ``` |
14 | 81 |
|
15 | | - A `Β§` must be immediately followed by ASCII digits (dot-separated for nested |
16 | | - levels, no spaces); the parsed section number is stored on the token's |
17 | | - `meta["numbers"]` as a list of ints (e.g. `[1, 1]` for `Β§1.1`). |
| 82 | + A `Β§` must be immediately followed by one or more `.`-separated levels of 1 to 9 ASCII digits, with no spaces. |
| 83 | + A trailing `.` is not part of the reference, and a reference directly followed by an ASCII letter (`Β§1a`), |
| 84 | + or with a level longer than nine digits, stays literal text. |
| 85 | + Each reference is a `section_ref` leaf token whose `content` is the text as written |
| 86 | + and whose `meta["numbers"]` lists each level as an int (e.g. `[1, 1]` for `Β§1.1`). |
| 87 | + The grammar and the token shape are documented in the plugin docstring and are public API from this release. |
| 88 | + It renders as `<span class="section-ref">Β§1.1</span>` by default. |
18 | 89 |
|
19 | 90 | **Requires markdown-it-py >= 4.1.0.** |
20 | 91 |
|
21 | | -- π FIX: `myst_role` no longer mis-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) |
| 92 | +### π Fixes |
| 93 | + |
| 94 | +- π FIX: `gfm_autolink` no longer hangs on autolink-like text in link labels (#160), thanks to [@AryanRG](https://github.com/AryanRG) ([#161](https://github.com/executablebooks/mdit-py-plugins/pull/161)) |
| 95 | +- π FIX: `texmath` no longer loops forever on block math opened in a blockquote and closed after it (#156), thanks to [@acaprino](https://github.com/acaprino) ([#157](https://github.com/executablebooks/mdit-py-plugins/pull/157)) |
| 96 | +- π FIX: `dollarmath`: a same-line `$$β¦$$` followed by text no longer swallows the following lines (#147), thanks to [@KyleKing](https://github.com/KyleKing) ([#149](https://github.com/executablebooks/mdit-py-plugins/pull/149)) |
| 97 | +- π FIX: `dollarmath`: multi-line `$$` blocks in a blockquote no longer keep the `>` prefixes in their content (#125), thanks to [@BetterAndBetterII](https://github.com/BetterAndBetterII) ([#152](https://github.com/executablebooks/mdit-py-plugins/pull/152)) |
| 98 | +- π FIX: `front_matter`: parse front matter closed by the YAML `...` document-end marker, thanks to [@feiiiiii5](https://github.com/feiiiiii5) ([#158](https://github.com/executablebooks/mdit-py-plugins/pull/158)) |
| 99 | + |
| 100 | + A block closed by `...` used to leak into the document as text when the `...` was the document's last line or had trailing spaces, |
| 101 | + and otherwise kept the `...` in `token.content`. |
| 102 | + The marker must be at column 0 and may be followed by spaces only. |
| 103 | + |
| 104 | +- π FIX: `footnote`: reserve an inline footnote's id before parsing its content, thanks to [@civilwarinfrance](https://github.com/civilwarinfrance) ([#162](https://github.com/executablebooks/mdit-py-plugins/pull/162)) |
| 105 | + |
| 106 | + A `[^named]` reference or a nested `^[...]` inside an inline footnote no longer loses the inner note, duplicates an id, or raises `KeyError: 'count'`. |
| 107 | + |
| 108 | +- π FIX: `anchors`: keep explicit heading ids, and do not strip slug titles, thanks to [@muneikh](https://github.com/muneikh) ([#159](https://github.com/executablebooks/mdit-py-plugins/pull/159)) |
| 109 | +- π FIX: `attrs`: unterminated groups, class accumulation and closing-token merge ([#153](https://github.com/executablebooks/mdit-py-plugins/pull/153)) |
| 110 | + |
| 111 | + An unterminated attribute group no longer swallows the text after it. |
| 112 | + Classes from adjacent attribute groups are combined on spans and links (`[a]{.x}{.y}` gives `class="x y"`), |
| 113 | + as they already were on inline code and images. |
| 114 | + An attributes block at the end of a container is no longer merged onto the closing token |
| 115 | + (`> {.a}` rendered `</blockquote class="a">`). |
| 116 | + |
| 117 | +- π FIX: `myst_role` no longer rejects a role at the start of inline content when the content ends with a backslash, |
| 118 | + and `` \\{name}`x` `` (an escaped backslash before a role) now parses the role (#62) ([#145](https://github.com/executablebooks/mdit-py-plugins/pull/145)) |
| 119 | + |
| 120 | +### π Documentation |
| 121 | + |
| 122 | +- π DOCS: Explain front matter extraction and HTML rendering (#133), thanks to [@Gonghan-Princess](https://github.com/Gonghan-Princess) ([#154](https://github.com/executablebooks/mdit-py-plugins/pull/154)) |
| 123 | +- π DOCS: A runnable usage example (#44), the `dollarmath` rendering contract and a "Rendering dollarmath output" guide (#111), |
| 124 | + and Read the Docs builds on Python 3.13 ([#166](https://github.com/executablebooks/mdit-py-plugins/pull/166)) |
| 125 | + |
| 126 | +### π§ Maintenance |
| 127 | + |
| 128 | +- π§ Require markdown-it-py >= 3.0, test the floor and Python 3.14 in CI, and update the classifiers ([#170](https://github.com/executablebooks/mdit-py-plugins/pull/170)) |
| 129 | +- π§ Releases are published to PyPI with trusted publishing (#141), with attestations ([#142](https://github.com/executablebooks/mdit-py-plugins/pull/142)), |
| 130 | + from distributions built with `python -m build` and checked on every pull request ([#164](https://github.com/executablebooks/mdit-py-plugins/pull/164)) |
| 131 | +- π§ pre-commit autoupdate (ruff 0.17, mypy 2.4) with a pinned ruff base rule set ([#138](https://github.com/executablebooks/mdit-py-plugins/pull/138)), |
| 132 | + and a non-blocking Codecov upload ([#167](https://github.com/executablebooks/mdit-py-plugins/pull/167)) |
| 133 | + |
| 134 | +**Full Changelog**: <https://github.com/executablebooks/mdit-py-plugins/compare/v0.6.1...v0.7.0> |
22 | 135 |
|
23 | 136 | ## 0.6.1 - 2026-05-13 |
24 | 137 |
|
|
0 commit comments