safehtml不安全是因为它仅禁用模板自动转义,不校验内容合法性;传入未过滤的或javascript:链接会原样执行,必须先用bluemonday.ugcpolicy()等白名单策略清洗。

不能直接用 {{.Content | safeHTML}},必须先用 bluemonday 过滤,再传入模板。 否则富文本里的 <img onerror="alert(1)"> 或 javascript: 链接会直接执行。
为什么 safeHTML 本身不安全
Go 模板的 safeHTML 只是告诉引擎“别转义”,它不检查内容是否合法。你传进去什么,它就原样吐出来。常见错误包括:
- 用户输入
@#@#@#@#@#@#@#@#@#@0,safeHTML照单全收 - 富文本编辑器导出的 HTML 带有
onload、onmouseover等事件属性,浏览器直接执行 - 嵌入的
<iframe srcdoc="<script>..."></iframe>或 base64 data URL 也被放行
用 bluemonday 做白名单过滤才是正解
它不是删掉“危险标签”,而是只保留你明确允许的元素和属性。典型配置:
- 普通用户评论 →
bluemonday.UGCPolicy():允许<a></a>、<strong></strong>、<em></em>,但自动删掉所有on*属性和javascript:、vbscript:协议 - 后台富文本(如商品详情)→
bluemonday.StrictPolicy():连style和class都默认剔除,更保守 - 自定义策略时,显式声明允许项:
policy.AllowAttrs("href").OnElements("a"),而不是写“禁止哪些”
示例代码(handler 中):
import "github.com/microcosm-cc/bluemonday"
policy := bluemonday.UGCPolicy()
cleaned := policy.Sanitize(inputHTML)
c.HTML(http.StatusOK, "page.html", gin.H{
"Content": cleaned, // 传入已清洗的字符串,模板里直接 {{.Content}}
})
模板里怎么写才不出错
过滤后的 HTML 是纯字符串,进模板后必须用 {{.Content | safeHTML}} 渲染,否则会被二次转义成乱码。但注意:
- 别在结构体字段上加
json:",string"或其他 tag 干扰序列化 - 如果传的是指针(如
*string),模板里要先判空:{{if .Content}}{{.Content | safeHTML}}{{end}},否则 nil 指针 panic - 不要把
bluemonday.Policy实例塞进模板数据——它不可序列化,会触发 panic
真正容易被忽略的是:清洗必须发生在服务端 handler 里,而不是前端或中间件里做一次就完事;不同业务场景(用户评论 vs 管理员发布)要用不同策略,混用会导致权限绕过或过度过滤。











