🚨 Shai-Hulud Strikes Again:834 Packages Compromised.Technical Analysis β†’
Socket
Book a DemoInstallSign in
Socket

fstmd

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fstmd

Finite-State Markdown Engine - O(N) single-pass Markdown to HTML converter using pure FST

pipPyPI
Version
1.0.0
Maintainers
1

FSTMD - Finite-State Markdown Engine

Python 3.13+ License: MIT Type Checked

A pure Finite State Transducer (FST) Markdown to HTML converter for Python 3.13+.

Features

  • ⚑ O(N) Single-Pass Processing - No backtracking, no regex, no AST
  • πŸ”’ Security First - XSS prevention with proper HTML escaping
  • 🎯 Zero Dependencies - Pure Python, no external packages required
  • πŸ“ Type Safe - Full type annotations, mypy strict compatible
  • πŸš€ Python 3.13+ Optimized - Uses latest Python features

Installation

pip install fstmd

Or install from source:

git clone https://github.com/fstmd/fstmd.git
cd fstmd
pip install -e .

Quick Start

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>

Supported Markdown Features

FeatureSyntaxOutput
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>
ParagraphsBlank line separated<p>...</p>

API Reference

Markdown Class

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>")

Convenience Functions

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>")

Security

FSTMD is designed with security as a primary concern:

Safe Mode (Default)

All HTML special characters are escaped:

  • < β†’ &lt;
  • > β†’ &gt;
  • & β†’ &amp;
  • " β†’ &quot;
  • ' β†’ &#x27;
md = Markdown(mode="safe")
result = md.render("<script>alert('xss')</script>")
# Output: <p>&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;</p>

Dangerous Pattern Detection

The library detects and can reject:

  • <script> tags
  • javascript: URLs
  • vbscript: URLs
  • data: URLs (except safe image types)

Architecture

Finite State Transducer Design

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-Level FST

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        BLOCK FST STATES                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                 β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                                  β”‚
β”‚   β”‚  START   β”‚ ─────────────────────────────────────────────┐   β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                                               β”‚   β”‚
β”‚        β”‚                                                      β”‚   β”‚
β”‚    β”Œβ”€β”€β”€β”΄β”€β”€β”€β”   '#'     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚   β”‚
β”‚    β”‚ LINE  │──────────►│ HEADING  │──► count #'s, emit <hN>  β”‚   β”‚
β”‚    β”‚ START β”‚           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β”‚   β”‚
β”‚    β””β”€β”€β”€β”¬β”€β”€β”€β”˜                                                  β”‚   β”‚
β”‚        β”‚                                                      β”‚   β”‚
β”‚        β”‚   '-'    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚   β”‚
β”‚        β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ LIST_ITEM     │──► emit <li>             β”‚   β”‚
β”‚        β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β”‚   β”‚
β”‚        β”‚                                                      β”‚   β”‚
β”‚        β”‚   '\n'   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚   β”‚
β”‚        β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ BLANK_LINE    │──► close paragraph       β”‚   β”‚
β”‚        β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β”‚   β”‚
β”‚        β”‚                                                      β”‚   β”‚
β”‚        β”‚  other   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚   β”‚
β”‚        └─────────►│ PARAGRAPH     │──► emit <p>              β”‚   β”‚
β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β”‚   β”‚
β”‚                                                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Design Decisions

  • Two-Character Lookahead Maximum - Disambiguates * vs ** vs ***
  • No Backtracking - All decisions are final
  • Output During Transitions - Mealy machine produces output as it processes
  • Immutable State Definitions - States are enums, transitions are cached

Performance

Complexity Guarantees

OperationTime ComplexitySpace Complexity
ParseO(N)O(N)
Per characterO(1)O(1)

Benchmarks

Tested on Python 3.13 with a medium-sized document (~500 chars):

LibraryAvg Time (ms)ThroughputRelative
FSTMD0.0510M chars/sec1.0x
markdown-it-py0.153.3M chars/sec0.33x
Python-Markdown0.301.7M chars/sec0.17x
CommonMark-Py0.351.4M chars/sec0.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)

Limitations

FSTMD focuses on speed and simplicity. It does not support:

  • Code blocks (fenced or indented)
  • Block quotes
  • Ordered lists
  • Links and images
  • Tables
  • Footnotes
  • HTML pass-through in safe mode

For full CommonMark compliance, use markdown-it-py.

Development

Setup

# 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

Project Structure

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

Building and Publishing

Build

# Install build tools
pip install build twine

# Build distribution
python -m build

# Check the build
twine check dist/*

Publish to PyPI

# Upload to TestPyPI first
twine upload --repository testpypi dist/*

# Upload to PyPI
twine upload dist/*

Contributing

Contributions are welcome! Please:

  • Fork the repository
  • Create a feature branch
  • Add tests for new functionality
  • Ensure all tests pass (pytest)
  • Ensure type checking passes (mypy fstmd)
  • Ensure linting passes (ruff check fstmd)
  • Submit a pull request

License

MIT License - see LICENSE file.

Acknowledgments

Inspired by:

Made with ❀️ for fast, secure Markdown parsing.

Keywords

markdown

FAQs

Did you know?

Socket

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.

Install

Related posts