Skip to content

✨ feat(clean): per-property inline-style value allowlist#561

Merged
gaborbernat merged 2 commits into
tox-dev:mainfrom
gaborbernat:feat/533-allowed-styles
Jul 7, 2026
Merged

✨ feat(clean): per-property inline-style value allowlist#561
gaborbernat merged 2 commits into
tox-dev:mainfrom
gaborbernat:feat/533-allowed-styles

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

Adds Policy.allowed_styles, a per-element, per-property value allowlist for the style attribute, porting sanitize-html's allowedStyles.

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 own tag or "*" (their pattern lists union, so either can admit it) and its value matches one of the patterns, using an unanchored re.search — the same semantics as sanitize-html's RegExp.test. Patterns are strings or precompiled re.Pattern, compiled once in Sanitizer.__init__.

Precedence: this narrows on top of the existing css_properties name allowlist and the non-configurable dangerous-value baseline, and never weakens it. A property still has to be in css_properties, and expression() or a url() with a disallowed scheme is dropped even when a caller's pattern would match it. When no rule applies to an element (its tag and "*" both absent), allowed_styles imposes nothing extra. The <style> element body stays governed by css_properties alone, since sanitize-html's allowedStyles targets inline styles.

The narrowing runs inside the existing style-scrub walk; the non-allowed_styles path keeps its current zero-overhead scrub.

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$"]}},
)
sanitize('<p style="color: #ff0000; text-align: justify; font-size: 40px">Hi</p>', policy)
# <p style="color: #ff0000">Hi</p>

Closes #533

🤖 Generated with Claude Code

@gaborbernat gaborbernat added enhancement New feature or request area:sanitizer clean() HTML sanitizer labels Jul 7, 2026
@codspeed-hq

codspeed-hq Bot commented Jul 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 63 untouched benchmarks
🆕 1 new benchmark
⏩ 18 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 test_feature[sanitize-styles-spec] N/A 12.6 ms N/A

Comparing gaborbernat:feat/533-allowed-styles (db93818) with main (2bc0633)

Open in CodSpeed

Footnotes

  1. 18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Add Policy.allowed_styles, a per-element, per-property value allowlist
for the style attribute keyed {tag: {property: [pattern, ...]}} with
"*" matching every tag, porting sanitize-html's allowedStyles. A
declaration survives only when its property is listed for the element's
tag or "*" (their pattern lists union) and its value matches one of the
patterns via an unanchored re.search.

This narrows on top of css_properties and the non-configurable
dangerous-value baseline: a property must still be in css_properties,
and expression() or a url() with a disallowed scheme is dropped even
when a pattern would admit it. The <style> element body stays governed
by css_properties alone. The non-allowed_styles path keeps its existing
zero-overhead scrub.

Closes tox-dev#533
Add the reference note, explanation, tutorial step, and a benchmark op
that the earlier PR left out.

- bench: sanitize-styles op over a style-attribute-rich input with a
  per-property value allowlist, auto-registered in core.OPERATIONS and
  gated on the spec corpus like the other sanitize ops.
- reference: an allowed_styles note and example on the Policy docs.
- explanation: docs/explanation/sanitizing.rst on why value validation
  narrows on top of the name allowlist and never weakens the baseline.
- tutorial: a sanitize() step with an allowed_styles Policy in
  getting-started.
@gaborbernat gaborbernat force-pushed the feat/533-allowed-styles branch from 2d4a2cb to db93818 Compare July 7, 2026 05:38
@gaborbernat gaborbernat merged commit 80acd81 into tox-dev:main Jul 7, 2026
48 checks passed
@gaborbernat gaborbernat deleted the feat/533-allowed-styles branch July 10, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:sanitizer clean() HTML sanitizer enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sanitizer: per-property regex validation of inline styles (allowedStyles)

1 participant