python
39 lines · 5 steps
Sanitizing user HTML with bleach
Whitelist safe tags and attributes, strip everything else, then harden auto-generated links against tabnabbing.
Explained by
highlit
1import bleach
2
3ALLOWED_TAGS = [
4 "a", "b", "strong", "i", "em", "u",
5 "p", "br", "ul", "ol", "li", "blockquote",
6 "code", "pre", "h1", "h2", "h3",
7]
8
9ALLOWED_ATTRIBUTES = {
10 "a": ["href", "title", "rel"],
11}
12
13ALLOWED_PROTOCOLS = ["http", "https", "mailto"]
14
15
16def sanitize_user_html(raw: str) -> str:
17 cleaned = bleach.clean(
18 raw,
19 tags=ALLOWED_TAGS,
20 attributes=ALLOWED_ATTRIBUTES,
21 protocols=ALLOWED_PROTOCOLS,
22 strip=True,
23 strip_comments=True,
24 )
25
26 linker = bleach.linkifier.Linker(
27 callbacks=[_force_noopener],
28 skip_tags=["pre", "code"],
29 )
30 return linker.linkify(cleaned)
31
32
33def _force_noopener(attrs, new=False):
34 href_key = (None, "href")
35 if href_key not in attrs:
36 return attrs
37 attrs[(None, "rel")] = "nofollow noopener noreferrer"
38 attrs[(None, "target")] = "_blank"
39 return attrs
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A whitelist of allowed tags and attributes is safer than trying to blacklist dangerous markup.
- 2Restricting URL protocols blocks javascript: and data: vectors that survive tag filtering.
- 3Auto-generated links should carry rel=noopener noreferrer to prevent reverse-tabnabbing attacks.
Related explainers
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
python
import re from functools import total_ordering from typing import Optional
Parsing and comparing semantic versions
regex
operator-overloading
sorting
Intermediate
7 steps
go
package httputil import ( "net"
Safely extracting the real client IP in Go
security
http
ip-spoofing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/sanitizing-user-html-with-bleach-explained-python-502d/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.