Introduction
Sometimes you need to allow users to submit HTML (rich text editors, markdown). Sanitization removes dangerous elements while preserving safe formatting.
Key Concepts
Sanitization: Removing or escaping dangerous HTML elements and attributes.
Allowlist: Only permit explicitly safe tags and attributes.
nh3: Recommended Rust-based HTML sanitizer (bleach was deprecated in 2023).
Real World Context
Rich text editors (TinyMCE, CKEditor) produce HTML that must be sanitized server-side before storage. Client-side sanitization is easily bypassed by sending raw POST requests. The bleach library served this role for years but was deprecated in 2023 in favor of the faster, Rust-based nh3.
Deep Dive
Using nh3
python# pip install nh3 (nh3 is the recommended HTML sanitizer; bleach was deprecated in 2023) import nh3 ALLOWED_TAGS = {'p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li', 'code', 'pre'} ALLOWED_ATTRS = { 'a': {'href', 'title'}, } def sanitize_html(html): return nh3.clean( html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, ) # Usage in model class Article(models.Model): content_raw = models.TextField() content_safe = models.TextField(editable=False) def save(self, *args, **kwargs): self.content_safe = sanitize_html(self.content_raw) super().save(*args, **kwargs)
Link Sanitization
pythonimport nh3 def sanitize_with_safe_links(html): return nh3.clean( html, tags={'a', 'p', 'br'}, attributes={'a': {'href'}}, url_schemes={'http', 'https', 'mailto'}, # Block javascript: )
Markdown to Safe HTML
pythonimport markdown import nh3 def markdown_to_safe_html(text): # Convert markdown to HTML html = markdown.markdown(text) # Sanitize the result return sanitize_html(html)
Common Pitfalls
- Sanitizing on display instead of on save — Sanitizing on every page load wastes CPU and risks inconsistency; sanitize once when saving to the database.
- Using an overly permissive allowlist — Allowing style attributes or event handler attributes (onclick, onerror) re-opens XSS vectors even in sanitized HTML.
Best Practices
- Sanitize on save, not display: Process once, serve many times.
- Keep allowlists minimal: Only allow tags you really need.
- Block javascript: URLs: Use protocol allowlists.
Summary
When allowing user HTML, sanitize with an allowlist approach using nh3. Process on save, keep allowlists minimal, and always block dangerous URL protocols.