######################### Sanitize untrusted HTML ######################### Clean untrusted HTML against an allowlist with :func:`turbohtml.clean.sanitize`, the ``bleach.clean`` successor, keeping only a safe subset of tags, attributes, and URL schemes. ************************* Sanitize untrusted HTML ************************* To clean user-submitted HTML the way ``bleach.clean`` did, use :func:`turbohtml.clean.sanitize`. A :class:`~turbohtml.clean.Policy` says what to keep (here the ``relaxed`` preset for typical user content), and a non-overridable baseline drops scripting and ``javascript:`` URLs no matter what the policy allows: .. testcode:: from turbohtml.clean import sanitize, Policy print(sanitize("

Hi link

", Policy.relaxed())) .. testoutput::

Hi link

<script>evil()</script> ************************************* Keep the output safe for a template ************************************* When sanitized HTML is later rendered through a client-side template engine (Angular, Vue, Mustache, EJS, ERB), a surviving ``{{ ... }}``, ``${ ... }``, or ``<% ... %>`` is a second injection point: the engine evaluates it after the sanitizer has passed. Set ``strip_template_markers`` to collapse every such run, in kept text and attribute values, to a single space, matching DOMPurify's ``SAFE_FOR_TEMPLATES``. .. code-block:: python from turbohtml.clean import sanitize, Policy policy = Policy.relaxed() policy = Policy(tags=policy.tags, attributes=policy.attributes, strip_template_markers=True) print(sanitize("

Hi {{ user.name }}

", policy)) #

Hi

*************************** Neutralize DOM clobbering *************************** When you allow ``id`` or ``name`` on kept elements, an attacker can set a value that collides with a built-in ``document`` or form property and shadow it through named access -- ```` makes ``form.attributes`` resolve to the input, ```` hides ``document.body``. The allowlist keeps the attribute because it is otherwise ordinary. Set ``isolate_named_props`` to prefix every kept ``id`` and ``name`` value with ``user-content-``, moving it out of the property namespace so no value can collide, matching DOMPurify's ``SANITIZE_NAMED_PROPS``. An already-prefixed value is left alone, so re-sanitizing is a fixpoint. .. testcode:: from turbohtml.clean import sanitize, Policy policy = Policy( tags=frozenset({"a", "input"}), attributes={"a": frozenset({"id", "href"}), "input": frozenset({"name"})}, isolate_named_props=True, ) print(sanitize('x', policy)) .. testoutput:: x ********************************************* Restrict inline styles to known-good values ********************************************* ``css_properties`` allowlists property *names*; ``allowed_styles`` narrows further by *value*, the way sanitize-html's ``allowedStyles`` does. Key it ``{tag: {property: [pattern, ...]}}``, with ``"*"`` as a tag matching every element. A declaration survives only when its property is listed for the element's tag or ``"*"`` and its value matches one of the patterns (an unanchored :func:`re.search`). This runs on top of ``css_properties`` and the dangerous-value baseline: a property must still be in ``css_properties``, and ``expression()`` or a ``url()`` with a disallowed scheme is dropped even if a pattern would admit it. .. testcode:: from turbohtml.clean import sanitize, Policy policy = Policy( tags=frozenset({"p"}), attributes={"p": frozenset({"style"})}, css_properties=frozenset({"color", "text-align"}), allowed_styles={"*": {"color": [r"^#[0-9a-f]{3,6}$"], "text-align": [r"^left$|^center$|^right$"]}}, ) print(sanitize('

Hi

', policy)) .. testoutput::

Hi

****************************** Rename tags while sanitizing ****************************** To rewrite a tag as it is cleaned -- ```` to ````, or the deprecated ``
`` to a ``
`` -- set ``transform_tags``, sanitize-html's ``transformTags``. Key it by source tag: a bare string renames, and a :class:`~turbohtml.clean.Transform` renames and adds attributes. The rename runs before the allowlist, so the renamed element is re-checked from scratch; a transform sets an element's name, never its safety. Mapping a tag to a disallowed or unsafe target (``script``) still drops it, and an added attribute is scrubbed with the element's own, so it must be allowlisted to survive. .. testcode:: from turbohtml.clean import sanitize, Policy, Transform policy = Policy( tags=frozenset({"strong", "em", "div"}), attributes={"div": frozenset({"class"})}, transform_tags={"b": "strong", "i": "em", "center": Transform("div", {"class": "legacy"})}, ) print(sanitize("
bold and italic
", policy)) .. testoutput::
bold and italic
************************************ Allow an app's own custom elements ************************************ To keep your own custom elements (````, ````) without enumerating every one in ``tags``, give the policy a matcher, DOMPurify's ``CUSTOM_ELEMENT_HANDLING``. ``custom_element_check`` receives an unlisted element's lowercased tag name and returns true to keep it; ``custom_attribute_check`` receives ``(tag, attribute_name)`` and decides which of a kept custom element's attributes survive. Only basic custom-element names (hyphenated, not a reserved name like ``annotation-xml``) reach the matcher, and the safety baseline still runs on whatever it keeps -- ``on*`` handlers, ``javascript:`` URLs, and dangerous styles are dropped regardless. .. testcode:: from turbohtml.clean import sanitize, Policy policy = Policy( tags=frozenset({"p"}), custom_element_check=lambda tag: tag.startswith("x-"), custom_attribute_check=lambda _tag, name: name.startswith("data-"), ) print(sanitize('

starsno

', policy)) .. testoutput::

stars<y-ad>no</y-ad>

Pass ``re.compile(r"^x-").search`` as the matcher to drive it from a regular expression, and set ``allow_customized_builtins`` to also keep an ``is`` attribute whose value names a custom element (``