Initial checklist
Affected packages and versions
@milkdown/crepe 7.20.0
@milkdown/transformer 7.20.0
The bug originates in mdast-util-to-markdown (transitive dep) and is already filed there as syntax-tree/mdast-util-to-markdown#73. Filing here so Milkdown users hitting this know about it, and in case Milkdown wants to apply a workaround in @milkdown/transformer while the upstream fix lands.
Link to runnable example
Reproducible directly in the official playground: https://milkdown.dev/playground
Steps to reproduce
- Open https://milkdown.dev/playground
- Clear both panes.
- Paste this exact markdown into the right (markdown source) pane:
<https://example.com/page.\>
That's an autolink with one trailing backslash before >.
- Click into the left (Crepe) editor.
- Press End, then Enter (any action that fires
markdownUpdated).
- Look at the right pane. The autolink is now
<https://example.com/page.\\> — backslashes doubled.
- Repeat steps 3–5 (paste the new content back). Counts go: 1 → 2 → 4 → 8 → 16 → 32 …
Pure-library reproducer (without the editor, to show this is in the serializer):
import { fromMarkdown } from 'mdast-util-from-markdown';
import { toMarkdown } from 'mdast-util-to-markdown';
let md = '<https://example.com/page.\\>'; // 1 backslash
for (let i = 1; i <= 21; i++) {
md = toMarkdown(fromMarkdown(md)).trim();
console.log(`Save ${i}: ${md.match(/\\\\/g).length} backslashes, ${md.length} bytes`);
}
// Save 1: 2 backslashes
// Save 10: 1024
// Save 20: 1048576 (~1 MB)
// Save 21: ~2 MB
Expected behavior
Autolink URL round-trips verbatim. <https://example.com/page.\> parses, then re-serializes back to <https://example.com/page.\>.
Per CommonMark §6.7, content inside <...> autolinks is interpreted literally — backslash escapes are not processed. So the serializer should not be inserting extra backslashes for characters inside an autolink destination.
Actual behavior
Every backslash in the autolink URL is doubled on each round-trip. Because the just-added backslash is itself followed by another backslash (which is ASCII punctuation), the next round-trip doubles again. Result is exponential growth in stored content.
Real-world impact
We hit this in production: a single stray \ in an evidence document's autolink URL ballooned to ~2 MB of consecutive backslashes after roughly 21 user edits. Database row size grew without bound.
The bug is particularly nasty because:
- It only triggers for autolinks (
<URL>), not [text](url) or  — those round-trip cleanly through character escapes, so it's easy to miss in tests.
- It needs
\ followed by ASCII punctuation inside the autolink — once seeded, every subsequent edit doubles silently.
Runtime
Chrome (also Firefox, Safari — pure JS bug)
OS
Any (reproduced on macOS and Linux)
Root cause (already isolated)
In mdast-util-to-markdown/lib/util/safe.js's escapeBackslashes(), every existing \ followed by ASCII punctuation gets a fresh \ prepended. Since CommonMark forbids escapes inside autolinks, the parser keeps \ literal, so the count compounds.
See syntax-tree/mdast-util-to-markdown#73 for upstream tracking.
Initial checklist
Affected packages and versions
@milkdown/crepe7.20.0@milkdown/transformer7.20.0The bug originates in
mdast-util-to-markdown(transitive dep) and is already filed there as syntax-tree/mdast-util-to-markdown#73. Filing here so Milkdown users hitting this know about it, and in case Milkdown wants to apply a workaround in@milkdown/transformerwhile the upstream fix lands.Link to runnable example
Reproducible directly in the official playground: https://milkdown.dev/playground
Steps to reproduce
>.markdownUpdated).<https://example.com/page.\\>— backslashes doubled.Pure-library reproducer (without the editor, to show this is in the serializer):
Expected behavior
Autolink URL round-trips verbatim.
<https://example.com/page.\>parses, then re-serializes back to<https://example.com/page.\>.Per CommonMark §6.7, content inside
<...>autolinks is interpreted literally — backslash escapes are not processed. So the serializer should not be inserting extra backslashes for characters inside an autolink destination.Actual behavior
Every backslash in the autolink URL is doubled on each round-trip. Because the just-added backslash is itself followed by another backslash (which is ASCII punctuation), the next round-trip doubles again. Result is exponential growth in stored content.
Real-world impact
We hit this in production: a single stray
\in an evidence document's autolink URL ballooned to ~2 MB of consecutive backslashes after roughly 21 user edits. Database row size grew without bound.The bug is particularly nasty because:
<URL>), not[text](url)or— those round-trip cleanly through character escapes, so it's easy to miss in tests.\followed by ASCII punctuation inside the autolink — once seeded, every subsequent edit doubles silently.Runtime
Chrome (also Firefox, Safari — pure JS bug)
OS
Any (reproduced on macOS and Linux)
Root cause (already isolated)
In
mdast-util-to-markdown/lib/util/safe.js'sescapeBackslashes(), every existing\followed by ASCII punctuation gets a fresh\prepended. Since CommonMark forbids escapes inside autolinks, the parser keeps\literal, so the count compounds.See syntax-tree/mdast-util-to-markdown#73 for upstream tracking.