- modules/breakwater exports Render(markdown) and Purify(html): goldmark GFM in its default safe mode (raw HTML stripped, unsafe links dropped), then a named bluemonday policy built from NewPolicy, not the UGC preset - the policy is the PHP golem15/forum HTMLPurifier config 1:1: the fixed element list, a[href|rel|target], http/https/mailto only, rel=nofollow on every link, target=_blank plus noopener on fully-qualified links - tests cover the PHP ForumMarkdownRenderTest corpus, links per scheme, blank input, the Purify allow-list and concurrent use; testable examples feed the docs page snippets - module README, root module table row, introduction.md Utilities row and docs/services/markdown.md (which gate serves user content vs admin fields) - go.mod and go.sum unchanged: bluemonday v1.0.27 and goldmark v1.8.6 were already pinned
51 lines
3.6 KiB
Markdown
51 lines
3.6 KiB
Markdown
---
|
||
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 <b onclick=\"x()\">this</b>.\n\n<script>alert(1)</script>"
|
||
html := breakwater.Render(post)
|
||
fmt.Println(html)
|
||
// Output:
|
||
// <p>See <a href="https://example.com/docs" rel="nofollow noopener" target="_blank">the docs</a> and this.</p>
|
||
```
|
||
|
||
```go src=modules/breakwater/example_test.go#ExamplePurify
|
||
// Content stored as HTML: the same allow-list, no Markdown pass.
|
||
stored := `<p onclick="x()">Hi <b>there</b><img src="a.jpg"></p><a href="tel:+48">call</a>`
|
||
clean := breakwater.Purify(stored)
|
||
fmt.Println(clean)
|
||
// Output:
|
||
// <p>Hi <b>there</b></p>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 <b onclick="x()">bold</b> more` renders to `<p>text bold more</p>`, 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.
|