🐛 FIX: resolve regex vulnerabilities#148
Conversation
Hoist inline and string regexes to module-level compiled patterns (amsmath, tasklists, dollarmath, anchors) so the ReDoS guard discovers them. Compiling is also faster for these hot paths. Inline re.match(str, ...) still compiles once and caches, but pays a cache lookup on every call, so a compiled object runs about 2.2x faster per call (102 vs 235 ns on py3.14). The one-time compile (5 to 59 us) is paid once either way, and module globals sidestep re._cache's 512-entry eviction. Benched with timeit (best-of-5, 2M calls) comparing compiled.match(text) against a cache-warm re.match(pattern, text).
chrisjsewell
left a comment
There was a problem hiding this comment.
Python already caches regex compilation, so it is not necessary to have any of these global variables
|
I know, but those are for the script in tests/test_redos.py to check them and compilation is on the µs scale compared to the overall overhead of Python + mdformat's architecture We could remove that general check file entirely if you don't want to have it fuzz test moving forward, but the alternative to use lazy compilation while also fuzzing is to walk the AST, which makes that script much more complicated TLDR: delete tests/test_redos.py and go back to lazy compilation? |
chrisjsewell
left a comment
There was a problem hiding this comment.
Reviewed by checking the branch out and measuring the before/after behavior directly. The core fixes are correct and the suite is green (544 passed).
ReDoS fixes — confirmed real and resolved:
texmath```mathblock rules: the unpatched^{3}math\s+?([^]+?)\s+?{3}grows super-linearly on `` ```math `` + a whitespace run with no closing fence — I measured **0.14 s / 1.1 s / 7.5 s** at 400 / 800 / 1600 chars. The rewritten pattern stays flat (**<3 ms** at 200k chars). Same class of fix applied toadmon`.- The generic
tests/test_redos.pyguard discovers all 27 shipped patterns (including the ones nested in texmath'srulesdict) and would catch a reverted texmath fix — verified.
Precompile cleanups (amsmath RE_OPEN, anchors, dollarmath label normalizer, tasklists): behavior-preserving, verified parity on valid/invalid inputs. Good hygiene to bundle in.
Three notes (details inline):
tests/test_admon.py— the newtest_title_parsing_redosdoesn't reproduce the blow-up; its input passes on the unpatched regex, so it doesn't guard the fix. Easy to strengthen.texmath— the new capture trims surrounding whitespace / rejects whitespace-only content. Harmless, just flagging.- No
CHANGELOG.mdentry. Since this is the security-relevant fix (and #149 added one), it'd be good to record it here too, ideally noting which plugins/flavors were affected.
Nothing here blocks merging once the admon test is tightened. Nice work, and thanks for adding the categorical guard rather than just patching the two regexes.
Generated by Claude Code
|
Thanks @KyleKing — yeah, let's go back to lazy compilation. To be clear, it's not the perf: you're right that the compile cost is µs-scale and immaterial next to Python startup + everything downstream. My concern is structural — I don't want the library's module layout (a set of new top-level On
If either feels like too much for this PR, I'm also happy to drop Generated by Claude Code |
Address confirm vulnerabilities from #143
Does not fully resolve the issue because there are unconfirmed/low risk vulnerabilities to consider and we don't know what was initially found that led to the ticket