html/template 会自动转义所有 {{ . }} 插值内容:使用 html/template 时,若变量通过 {{ .var }} 直接插入,go 渲染时自动对 html 特殊字符转义。

html/template 会自动转义所有 .{{ }} 插值内容
只要用的是 html/template(不是 text/template),且变量通过 {{ .Var }} 直接插入,Go 就会在渲染时对 HTML 特殊字符做转义:比如 → <code>,<code>" → "。这是默认行为,不用额外配置。
常见错误现象:template.Execute 输出的页面里,用户输入的 <script>alert(1)</script> 显示为纯文本,没执行——这反而是对的;如果它真执行了,说明你误用了 text/template,或手动绕过了转义。
- 使用场景:渲染用户昵称、评论、文章标题等普通字段,直接
{{ .Title }}即可 - 注意
html/template和text/template是两个包,导入路径不同:html/template才有 HTML 上下文感知 - 转义基于当前上下文:在
href属性里插值会做 URL 编码,在style里会做 CSS 转义,机制比简单替换更细粒度
需要插入原始 HTML 时,必须显式调用 template.HTML
如果你真要渲染可信的富文本(比如后台管理员编辑的公告),不能靠 {{ .Content }},否则会被转义成一堆 <p></p>。必须先用 template.HTML 包装变量,再传给模板。
常见错误现象:调用 {{ .Content | safeHTML }} 却没生效——safeHTML 是旧版写法,Go 1.12+ 已弃用;或者忘了在 Go 代码里把字符串转成 template.HTML 类型。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:在 handler 中把字符串转类型:
data.Content = template.HTML(rawHTML) - 模板里仍写
{{ .Content }},不要加任何 filter - 绝不能对用户输入调用
template.HTML—— 这等于主动打开 XSS 大门 - 性能影响极小,但安全责任完全转移到调用方,容易漏检
属性插值必须用引号包裹,且避免 javascript: 等危险协议
即使 html/template 会转义,如果写成 <a href="https://www.php.cn/link/fedffbe61a89c5dfcaf78473300cb3c7"></a>(没引号),Go 无法准确判断上下文,可能漏转义。更糟的是,若 .URL 是 javascript:alert(1),浏览器仍会执行。
常见错误现象:点击链接触发脚本,控制台报错 Refused to execute a JavaScript URL —— 这是现代浏览器的防护,但旧版或某些环境仍可能执行。
- 属性值一律用双引号:
<a href="https://www.php.cn/link/fedffbe61a89c5dfcaf78473300cb3c7"></a>,让模板知道这是 HTML 属性上下文 - 禁止在
href、src、on*事件中插入用户可控内容,哪怕加了引号也不行 - 如需动态 URL,应在后端白名单校验协议:
if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { url = "/" }
自定义函数返回 HTML 时,必须返回 template.HTML 类型
写模板函数(比如 markdown)时,如果函数内部做了 HTML 渲染,返回值类型必须是 template.HTML,否则模板引擎仍会二次转义。
常见错误现象:自定义 funcMap 里返回 string,结果 markdown 渲染出的 <p></p> 标签被转义成 <p></p>。
- 函数签名要写成:
func(text string) template.HTML - 函数体内必须用
return template.HTML(htmlBytes),不能只 return string - 和变量一样,这个转换不提供任何过滤,输入必须已清洗或可信
- 如果函数逻辑复杂,建议把清洗/转义逻辑放在 Go 层,而非模板里
最易被忽略的一点:模板嵌套时,子模板接收的数据若来自父模板未转义的变量,依然会继承原始类型。也就是说,template.HTML 的“信任标记”会穿透 {{ template "sub" . }},别以为换了个模板就安全了。










