
Security News
Deno 2.6 + Socket: Supply Chain Defense In Your CLI
Deno 2.6 introduces deno audit with a new --socket flag that plugs directly into Socket to bring supply chain security checks into the Deno CLI.
fstmd
Advanced tools
A pure Finite State Transducer (FST) Markdown to HTML converter for Python 3.13+.
pip install fstmd
Or install from source:
git clone https://github.com/fstmd/fstmd.git
cd fstmd
pip install -e .
from fstmd import Markdown
# Create a parser (safe mode by default)
md = Markdown(mode="safe")
# Render Markdown to HTML
html = md.render("**Hello** *world*")
print(html)
# Output: <p><strong>Hello</strong> <em>world</em></p>
| Feature | Syntax | Output |
|---|---|---|
| Bold | **text** | <strong>text</strong> |
| Italic | *text* | <em>text</em> |
| Bold+Italic | ***text*** | <strong><em>text</em></strong> |
| Headings | # H1 to ###### H6 | <h1> to <h6> |
| Unordered Lists | - item | <ul><li>item</li></ul> |
| Paragraphs | Blank line separated | <p>...</p> |
from fstmd import Markdown
# Safe mode (default) - escapes all HTML
md = Markdown(mode="safe")
# Raw mode - passes through HTML (use with trusted input only!)
md = Markdown(mode="raw")
# Strict mode - raises exceptions on security issues
md = Markdown(mode="safe", strict=True)
# Render markdown
html = md.render("# Hello **World**")
# Always render safely, regardless of instance mode
safe_html = md.render_safe("<script>alert(1)</script>")
from fstmd.parser import render, render_unsafe
# Quick render with caching
html = render("**bold**")
# Render without escaping (dangerous!)
html = render_unsafe("<b>raw html</b>")
FSTMD is designed with security as a primary concern:
All HTML special characters are escaped:
< β <> β >& β &" β "' β 'md = Markdown(mode="safe")
result = md.render("<script>alert('xss')</script>")
# Output: <p><script>alert('xss')</script></p>
The library detects and can reject:
<script> tagsjavascript: URLsvbscript: URLsdata: URLs (except safe image types)FSTMD uses a Mealy Machine (FST) where output is generated during state transitions.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INLINE FST STATES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββ '*' ββββββββββββ '*' ββββββββββββ β
β β TEXT βββββββββββββΊβ STAR_ONE ββββββββββββΊβ STAR_TWO β β
β βββββ¬ββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β β β β β
β β other β other β other β
β βΌ βΌ βΌ β
β output char start italic start bold β
β β
β βββββββββββββ ββββββββββββββ β
β β IN_ITALIC ββββββββββ STAR_ONE β (from TEXT with '*') β
β βββββββ¬ββββββ ββββββββββββββ β
β β '*' β
β βΌ β
β ββββββββββββββββ β
β β close italic ββββββΊ output </em>, goto TEXT β
β ββββββββββββββββ β
β β
β ββββββββββββ βββββββββββββββββ β
β β IN_BOLD βββββββββββ STAR_TWO β (from STAR_ONE) β
β ββββββ¬ββββββ βββββββββββββββββ β
β β '**' β
β βΌ β
β βββββββββββββββ β
β β close bold ββββββββββΊ output </strong> β
β βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BLOCK FST STATES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββ β
β β START β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββ¬ββββββ β β
β β β β
β βββββ΄ββββ '#' ββββββββββββ β β
β β LINE ββββββββββββΊβ HEADING ββββΊ count #'s, emit <hN> β β
β β START β ββββββββββββ β β
β βββββ¬ββββ β β
β β β β
β β '-' βββββββββββββββββ β β
β βββββββββββΊβ LIST_ITEM ββββΊ emit <li> β β
β β βββββββββββββββββ β β
β β β β
β β '\n' βββββββββββββββββ β β
β βββββββββββΊβ BLANK_LINE ββββΊ close paragraph β β
β β βββββββββββββββββ β β
β β β β
β β other βββββββββββββββββ β β
β βββββββββββΊβ PARAGRAPH ββββΊ emit <p> β β
β βββββββββββββββββ β β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* vs ** vs ***| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Parse | O(N) | O(N) |
| Per character | O(1) | O(1) |
Tested on Python 3.13 with a medium-sized document (~500 chars):
| Library | Avg Time (ms) | Throughput | Relative |
|---|---|---|---|
| FSTMD | 0.05 | 10M chars/sec | 1.0x |
| markdown-it-py | 0.15 | 3.3M chars/sec | 0.33x |
| Python-Markdown | 0.30 | 1.7M chars/sec | 0.17x |
| CommonMark-Py | 0.35 | 1.4M chars/sec | 0.14x |
Note: Benchmarks vary by hardware and document structure.
Run benchmarks yourself:
from fstmd.benchmarks import run_benchmarks, print_benchmark_results
results = run_benchmarks()
print_benchmark_results(results)
FSTMD focuses on speed and simplicity. It does not support:
For full CommonMark compliance, use markdown-it-py.
# Clone the repository
git clone https://github.com/fstmd/fstmd.git
cd fstmd
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Type checking
mypy fstmd
# Linting
ruff check fstmd
fstmd/
βββ __init__.py # Package exports
βββ __main__.py # CLI entry point
βββ parser.py # High-level Markdown class
βββ exceptions.py # Custom exceptions
βββ core/
β βββ __init__.py
β βββ fsm.py # Main FST engine
β βββ states.py # State definitions
β βββ transitions.py # Transition table
β βββ safe_html.py # HTML escaping
βββ benchmarks/
β βββ __init__.py
β βββ runner.py # Benchmark utilities
βββ tests/
βββ conftest.py
βββ test_inline.py
βββ test_blocks.py
βββ test_security.py
βββ test_fsm.py
βββ test_integration.py
# Install build tools
pip install build twine
# Build distribution
python -m build
# Check the build
twine check dist/*
# Upload to TestPyPI first
twine upload --repository testpypi dist/*
# Upload to PyPI
twine upload dist/*
Contributions are welcome! Please:
pytest)mypy fstmd)ruff check fstmd)MIT License - see LICENSE file.
Inspired by:
Made with β€οΈ for fast, secure Markdown parsing.
FAQs
Finite-State Markdown Engine - O(N) single-pass Markdown to HTML converter using pure FST
We found that fstmd demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Deno 2.6 introduces deno audit with a new --socket flag that plugs directly into Socket to bring supply chain security checks into the Deno CLI.

Security News
New DoS and source code exposure bugs in React Server Components and Next.js: whatβs affected and how to update safely.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.