"""HTML sanitization utilities for XSS protection.""" import html from typing import Literal import streamlit as st # Valid heading levels HeadingLevel = Literal[1, 2, 3, 4, 5, 6] def escape_html(text: str) -> str: """Escape HTML special characters to prevent XSS. Args: text: Raw text that may contain HTML Returns: Escaped text safe for HTML insertion """ return html.escape(str(text)) def safe_heading( text: str, level: HeadingLevel = 1, color: str = "steelblue", align: str = "center", ) -> None: """Render a heading with escaped text to prevent XSS. Args: text: Heading text (will be escaped) level: Heading level 1-6 color: CSS color value align: CSS text-align value """ # Escape all user-provided values safe_text = escape_html(text) safe_color = escape_html(color) safe_align = escape_html(align) st.markdown( f"" f"{safe_text}", unsafe_allow_html=True, ) def safe_paragraph( text: str, color: str = "white", align: str = "center", ) -> None: """Render a paragraph with escaped text to prevent XSS. Args: text: Paragraph text (will be escaped) color: CSS color value align: CSS text-align value """ safe_text = escape_html(text) safe_color = escape_html(color) safe_align = escape_html(align) st.markdown( f"

" f"{safe_text}

", unsafe_allow_html=True, ) def safe_styled_text( text: str, tag: str = "span", color: str | None = None, align: str | None = None, **styles: str, ) -> str: """Generate HTML string with escaped text and validated styles. Args: text: Text content (will be escaped) tag: HTML tag to use color: Optional CSS color align: Optional CSS text-align **styles: Additional CSS properties Returns: Safe HTML string """ safe_text = escape_html(text) safe_tag = escape_html(tag) style_parts: list[str] = [] if color: style_parts.append(f"color: {escape_html(color)}") if align: style_parts.append(f"text-align: {escape_html(align)}") for prop, value in styles.items(): # Convert underscores to hyphens for CSS properties css_prop = prop.replace("_", "-") style_parts.append(f"{escape_html(css_prop)}: {escape_html(value)}") style_str = "; ".join(style_parts) if style_str: return f"<{safe_tag} style='{style_str}'>{safe_text}" return f"<{safe_tag}>{safe_text}"