Beyond HTML auto-escaping, Kida provides context-specific escaping utilities for framework authors who need to embed user data in JavaScript, CSS, and URL contexts safely.
All functions are O(n) single-pass with no regex in the hot path and no ReDoS risk.
from kida.utils.html import js_escape, css_escape, url_is_safe, safe_url, xmlattr
Context-Specific Escaping
HTML escaping alone isn't enough when embedding values in different contexts. Each context has different dangerous characters:
| Context | Function | Danger |
|---|---|---|
| HTML content | html_escape() |
<, >, &, ", 'break markup |
| JavaScript strings | js_escape() |
", ', `, \n, </script>break string literals |
| CSS values | css_escape() |
(, ), \, <enable injection |
| URL attributes | url_is_safe() |
javascript:, data:execute code |
JavaScript Escaping
Usejs_escape()when embedding user data inside JavaScript string literals:
from kida.utils.html import js_escape
user_input = 'Hello "World" </script><script>alert(1)'
safe = js_escape(user_input)
# 'Hello \\"World\\" \\x3c\\/script\\x3e\\x3cscript\\x3ealert(1)'
What it escapes:
- String delimiters:
",',`(template literals) - Template literal interpolation:
$(prevents${...}injection) - Newlines:
\n,\r,\t - Script-breaking:
<,>,/(prevents</script>breakout) - Line separators:
U+2028,U+2029(break JS strings) - NUL bytes:
\x00(bypass attempts)
# Safe to embed in inline scripts
html = f'<script>var name = "{js_escape(user_input)}";</script>'
Warning:
js_escape()is for string literals only. For JSON data, usejson.dumps(). For numeric values, validate withint()orfloat().
JSString
Wrap escaped values to prevent accidental double-escaping:
from kida.utils.html import js_escape, JSString
safe = JSString(js_escape(user_input))
# Type-safe marker, similar to Markup for HTML
CSS Escaping
Usecss_escape()when embedding values in CSS property values:
from kida.utils.html import css_escape
user_color = 'red; background: url(javascript:alert(1))'
safe = css_escape(user_color)
# 'red; background: url\\(javascript:alert\\(1\\)\\)'
What it escapes: \, ", ', (, ), /, <, >, &, NUL bytes.
URL Validation
url_is_safe
Check if a URL has a safe protocol scheme before using it inhref or srcattributes:
from kida.utils.html import url_is_safe
url_is_safe("https://example.com") # True
url_is_safe("/path/to/page") # True (relative)
url_is_safe("javascript:alert(1)") # False
url_is_safe(" javascript:alert(1) ") # False (whitespace stripped)
url_is_safe("data:text/html,<h1>Hi</h1>") # False (by default)
url_is_safe("data:image/png;base64,...", allow_data=True) # True
Safe schemes: http, https, mailto, tel, ftp, ftps, sms.
The function uses window-based parsing (O(n) single pass, no regex) and strips NUL bytes before validation.
safe_url
Return a fallback value when a URL is unsafe:
from kida.utils.html import safe_url
safe_url("https://example.com") # 'https://example.com'
safe_url("javascript:alert(1)") # '#'
safe_url("javascript:void(0)", fallback="/home") # '/home'
Attribute Validation
xmlattr
Convert a dictionary to HTML attributes with validation and escaping:
from kida.utils.html import xmlattr
attrs = xmlattr({"class": "btn primary", "data-id": "123", "disabled": True})
# Markup('class="btn primary" data-id="123" disabled="True"')
Nonevalues are skipped:
xmlattr({"class": "btn", "id": None})
# Markup('class="btn"')
Event Handler Detection
By default,xmlattr()warns when it encounters event handler attributes (potential XSS vectors):
xmlattr({"onclick": "handleClick()"})
# UserWarning: Event handler attribute 'onclick' can execute JavaScript.
Kida tracks 84 event handler attributes from the WHATWG HTML Living Standard, including mouse, keyboard, focus, form, drag, clipboard, media, pointer, animation, and transition events.
Control the behavior:
# Suppress warnings (you've verified the handlers are safe)
xmlattr({"onclick": "handler()"}, allow_events=True)
# Automatically strip event handlers
xmlattr({"onclick": "handler()", "class": "btn"}, strip_events=True)
# Markup('class="btn"')
Attribute Name Validation
Attribute names are validated per the HTML5 spec. Invalid names (containing whitespace, NUL, quotes,>, /, =) are rejected:
# Strict mode (default) — raises ValueError
xmlattr({"invalid name": "value"}) # ValueError
# Lenient mode — skips with warning
xmlattr({"invalid name": "value"}, strict=False) # Markup('')
NUL Byte Stripping
All escaping functions strip\x00(NUL) bytes. NUL bytes can be used to bypass filters in some contexts:
from kida.utils.html import html_escape
html_escape("safe\x00<script>")
# 'safe<script>' (NUL removed, script escaped)
format_html
A convenience function for building HTML with escaped arguments:
from kida.utils.html import format_html
html = format_html("<p>Hello, {name}!</p>", name="<script>")
# Markup('<p>Hello, <script>!</p>')
html = format_html("<a href='{url}'>{text}</a>", url="/page", text="<Click>")
# Markup("<a href='/page'><Click></a>")
Warning: The format string itself is not escaped. Only use with trusted format strings.
API Reference
| Function | Signature | Description |
|---|---|---|
js_escape() |
(value: Any) -> str |
JavaScript string literal escaping |
css_escape() |
(value: Any) -> str |
CSS property value escaping |
url_is_safe() |
(url: str, *, allow_data: bool = False) -> bool |
Protocol scheme validation |
safe_url() |
(url: str, *, fallback: str = "#") -> str |
Safe URL with fallback |
xmlattr() |
(value: dict, *, allow_events, strip_events, strict) -> Markup |
Dict to HTML attributes |
format_html() |
(format_string: str, *args, **kwargs) -> Markup |
HTML formatting with escaping |
JSString |
class | Safe string marker for JS context |
All functions are importable fromkida.utils.html.
See Also
- Escaping — HTML auto-escaping in templates
- T-Strings — Auto-escaping with PEP 750
- Filter Reference —
escape,safe,tojsonfilters