Files
Jakub Zych 91593da1f7 test(17-06): framework and admin SPA unit tests for the blog companions
- signpost: activation refusals, host overrides, D-02 pattern layers
  incl. PatternFrom, ReferenceOptions depths
- seaglass: removal report, bluemonday bypass corpus, Check
- pact Option.Depth omitempty and the menu item type contracts; party
  activates menu item types after Register and before the first Boot
- cabana: repeater compile and decode, dependsOn compile and recheck,
  richeditor keys and notices, reorder and singleton boot checks
  (Docker-free); repeater save, dependent recheck, richeditor save,
  reorder and singleton routes on Postgres; conformance cases for the
  documented 404, 409 and 422 answers of the new routes
- admin: richeditor value contract, link popover, notices and chunk
  failure; repeater bounds, remove and reorder, collapsed rows;
  dependent dropdown states and last request wins; reorder
  announcements, partition switch and tree rules; SingletonForm spec
- gate: required names in the go, admin and postgres stages
2026-10-12 19:29:37 +02:00
..

seaglass

HTML sanitizer for stored rich text: a bluemonday allowlist policy that returns clean HTML byte for byte and reports what it removed from the rest.

import "git.golem15.com/golem15/summercms/modules/seaglass"

Name: Glass the sea has worn smooth; rough HTML goes in and only the safe, smooth part comes out.

Overview

seaglass is the framework's one HTML policy for rich text an administrator stores, such as cabana's type: richeditor field. It wraps bluemonday v1.0.27 with the policy the Journal plugin uses for imported WinterCMS content, without that plugin's editor-specific rules. A tokenizing sanitizer checks attributes and URLs the way a browser parses them, so it does not share the bypasses of regex gates (<img src="x"onerror=…>, javascript&#58;, <svg/onload=…>).

The comparison is byte-stable: when the input and the policy's output tokenize to the same HTML tokens, the input itself is returned, so a value that only differs in escaping is never rewritten. Otherwise the policy's output is returned with a report of the removed elements, attributes and comments, which cabana shows to the administrator as save notices.

Role in the framework

A leaf utility with no SummerCMS dependency. cabana runs every richeditor value through it on create and update, top level and inside repeater rows; plugins that serve stored HTML in a public API can run it again on output, for content that was imported without passing the admin save.

Use cases:

  • Sanitize an HTML field on save and tell the editor what was removed.
  • Check that stored HTML is already clean (seaglass.Check), for example in an import report.
  • Sanitize HTML on output in a public API.

Features

  • bluemonday's UGC policy with links kept as written (no rel="nofollow"), tel: links next to http, https, mailto and relative URLs, and the class, dir, microdata, footnote role and inline color, font-size, font-weight and text-align styles imported WinterCMS content uses.
  • Everything else is removed: script, style, iframe, object, embed, form, svg, math, comments, every on* attribute, target, rel, and URLs with any other scheme.
  • seaglass.SanitizeReport returns HTML the policy keeps whole byte for byte with a zero seaglass.Removals; a tag that repeats an attribute is always a change.
  • seaglass.Removals names the removed elements and attributes (at most ten distinct names each, in first-seen order) and counts every removal and every removed comment.

Usage

clean, report := seaglass.SanitizeReport(`<p onclick="x()">Hi</p><script>alert(1)</script>`)
// clean == "<p>Hi</p>"
// report.Elements == []string{"script"}, report.Attributes == []string{"onclick"}

if err := seaglass.Check(stored); err != nil {
	// stored HTML would change under the policy
}

API reference

Identifier Description
seaglass.Sanitize Returns the HTML filtered through the policy: the input itself when the policy keeps all of it.
seaglass.SanitizeReport seaglass.Sanitize with a seaglass.Removals report of what was removed; a zero report exactly when the input is returned.
seaglass.Check Returns an error when the policy would change the HTML, nil when it keeps it whole.
seaglass.Removals What was removed: Elements and Attributes (distinct names), ElementCount and AttributeCount (every removal), Comments, and Changed (the HTML differs from the input).

Dependencies

  • SummerCMS modules: none.
  • Third-party: github.com/microcosm-cc/bluemonday v1.0.27 (the policy; decision note .planning/notes/2026-10-11-framework-html-sanitizer-bluemonday.md), golang.org/x/net/html (the token comparison and the removal report).
  • Standard library: errors, fmt, io, regexp, slices, strings.

Testing

go test ./modules/seaglass/...

The tests run in memory and need no external services.