--- title: Markdown and HTML sanitizing description: Render user Markdown to safe HTML and sanitize stored HTML with the breakwater allow-list; which gate serves admin fields instead. section: services order: 165 --- # Markdown and HTML sanitizing A plugin that stores content a site user submits, such as forum posts, comments or HTML imported from another system, serves that content on a public page. [breakwater](../../modules/breakwater/README.md) is the one gate it goes through: `breakwater.Render` turns Markdown into HTML and `breakwater.Purify` sanitizes content that is already HTML, both with the same fixed allow-list. The plugin calls the gate on write, stores the result and serves it as is. ## The two calls ```go src=modules/breakwater/example_test.go#ExampleRender // A forum post: store the rendered HTML on write and serve it as is. post := "See [the docs](https://example.com/docs) and this.\n\n" html := breakwater.Render(post) fmt.Println(html) // Output: //

See the docs and this.

``` ```go src=modules/breakwater/example_test.go#ExamplePurify // Content stored as HTML: the same allow-list, no Markdown pass. stored := `

Hi there

call` clean := breakwater.Purify(stored) fmt.Println(clean) // Output: //

Hi there

call ``` `breakwater.Render` is GitHub Flavored Markdown: tables, fenced code, strikethrough, links and autolinks render; `breakwater.Purify` is the policy alone, for content a plugin keeps as HTML. Neither returns an error: both return the policy's output, and `""` for blank or whitespace-only input. ## The policy The allow-list is the PHP `golem15/forum` HTMLPurifier config, 1:1. | Rule | Value | |------|-------| | Allowed elements | `p`, `br`, `strong`, `em`, `b`, `i`, `h1`–`h6`, `ul`, `ol`, `li`, `blockquote`, `pre`, `code`, `a`, `table`, `thead`, `tbody`, `tr`, `th`, `td`, `hr`, `del`, `ins`, `sub`, `sup`. Everything else is removed, including `img`, `figure`, `script`, `style`, `iframe`, `form`, `svg` and every `on*` attribute; `class` and `id` are dropped from the elements that stay. | | `a` attributes | `href`, `rel` and `target`. A caller-supplied `rel` or `target` with a value the policy would not write itself is dropped and rewritten. | | URL schemes | `http`, `https` and `mailto`. No `tel:` and no relative URLs; a link whose `href` is refused loses its tag and keeps its text. | | Link rewriting | Every link gets `rel="nofollow"`; every fully-qualified `http` or `https` link also gets `target="_blank"` and `rel="noopener"`. | Raw HTML in Markdown is stripped, not escaped into visible text: `text bold more` renders to `

text bold more

`, with neither the tag nor an `<b>` in the output. HTML comments are removed. A Markdown link to a `javascript:`, `data:` or `vbscript:` destination is dropped before the policy runs, and its text stays. ## Which gate - `breakwater` for user content: anything a site user submits that a plugin stores and serves, in Markdown or HTML. - [seaglass](../../modules/seaglass/README.md) for administrator rich text saved through cabana's `richeditor` field (see [Forms](../backend/forms.md)): bluemonday's UGC policy, which keeps images, relative and `tel:` links, and links as written without `nofollow`. `seaglass.Sanitize` is not a gate for user content. - `cabana.RenderMarkdown` for the admin Markdown field preview only: CommonMark without GFM behind a regex gate that refuses unsafe output instead of removing it.