Go html/template 生成 javascript URL 为什么被过滤
来源:17golang原创
时间:2026-10-20 20:43:56 450浏览 收藏
如果 Go 的 html/template 把你传入的 javascript:... 输出成 #ZgotmplZ,通常不是模板写错了,而是它在 href 这类 URL 上下文中主动拦截了危险协议。这样做是为了避免不可信数据变成可执行脚本。
官方地址:https://pkg.go.dev/html/template
这类问题的处理顺序很简单:先确认需求是不是“跳转到一个地址”,如果是,就改成 https 或站内路径;如果确实需要特殊协议,再由服务端对固定来源做 allowlist,最后才考虑 template.URL。不要把用户输入直接强转成可信类型。
为什么 javascript: 会变成 #ZgotmplZ
html/template 和 text/template 的关键区别是,它会结合 HTML、CSS、JavaScript、URI 等上下文进行转义。href="{{.URL}}" 中的值不是普通文本,而是一个可能影响浏览器行为的 URL。
对于普通字符串,模板包会检查协议。http:、https: 和 mailto: 属于文档明确列出的允许协议;其它协议会按不安全处理。命中 URL 过滤器后,输出中的值会被替换成 #ZgotmplZ,这正是它的运行时占位标记。

普通字符串和 template.URL 不是一回事
默认情况下,模板把数据当成不可信的普通文本。下面的例子用同一个模板展示普通字符串和可信 URL 类型的职责差异:前者会被过滤,后者表示调用方已经完成了额外的安全判断。
package main
import (
"os"
"html/template"
)
func main() {
// URL 上下文会对普通字符串做协议过滤。
t := template.Must(template.New("link").Parse(`打开`))
_ = t.Execute(os.Stdout, "javascript:alert('blocked')")
// template.URL 只适合来自可信、固定来源的已审查值。
trusted := template.URL("javascript:trustedAction()")
_ = t.Execute(os.Stdout, trusted)
}
这里的第二种写法不是“关闭安全检查”的通用修复方案。官方文档明确提示,template.URL 的内容会按原样进入模板,因此只要来源可被用户控制,类型转换本身就可能制造注入风险。多数业务不应该让动态脚本通过 href 传递。
更稳妥的改法:让链接表达导航
如果点击动作本质上是打开页面、提交确认或进入下一步,优先把它建模成一个普通 HTTP 路由。脚本需要的业务参数放进查询参数或服务端会话中,模板只负责输出可导航的 URL。
package main
import (
"html/template"
"net/url"
"os"
)
type PageData struct {
ActionURL string
}
func main() {
// 用 url.Values 编码参数,避免手工拼接破坏 URL 结构。
q := url.Values{}
q.Set("next", "/orders")
data := PageData{ActionURL: "/confirm?" + q.Encode()}
// 普通站内路径由 html/template 按 URL 上下文安全输出。
t := template.Must(template.New("page").Parse(`继续`))
_ = t.Execute(os.Stdout, data)
}
如果前端需要根据状态决定行为,可以输出受限的 data-action 值,让已经加载的脚本通过事件监听器查表处理。这样“可执行代码”和“服务端数据”仍然是两条边界,模板不会把任意字符串当作 JavaScript。

必须使用特殊协议时怎样收紧边界
少数内嵌应用可能确实有固定的特殊协议需求。这时要把“允许哪些完整字符串”写成代码中的固定集合,而不是接收一个任意 URL 后调用 template.URL(value)。
package main
import (
"errors"
"html/template"
"strings"
)
var errUnsafeURL = errors.New("unsafe action URL")
func trustedActionURL(name string) (template.URL, error) {
// 只匹配固定动作,不把 name 拼进可执行脚本。
switch strings.TrimSpace(name) {
case "open-help":
return template.URL("javascript:openHelp()"), nil
case "close-panel":
return template.URL("javascript:closePanel()"), nil
default:
return "", errUnsafeURL
}
}
即使这样,也要确认固定脚本不包含外部输入,并把这段转换集中在少数可审查函数中。更推荐使用普通链接和事件映射;allowlist 只能缩小风险,不能把 javascript: 变成适合任意数据的协议。
几个容易混淆的边界
| 现象 | 实际含义 | 处理方式 |
|---|---|---|
#ZgotmplZ | 不安全值到达 URL 或 CSS 上下文 | 检查协议和数据来源,不要只改输出字符串 |
template.URL | 调用方声明内容已经可信 | 只接收固定或严格 allowlist 的值 |
| JavaScript 上下文,不是 href URL 上下文 | 传递结构化数据,让模板负责 JS 安全编码 |
template.HTML | 声明整段 HTML 已可信 | 不要拿它解决 URL 协议问题 |
排查结论
遇到 “javascript URL 被过滤” 时,可以按下面的顺序处理:
- 确认占位值是否来自
href、src等 URL 上下文。 - 检查输入是不是普通字符串,以及协议是否为
http、https或站内相对路径。 - 若需求只是导航,改用普通路由和参数编码。
- 若确有特殊协议,先做固定值 allowlist,再集中转换为
template.URL。 - 不要用
template.HTML、字符串替换或关闭转义来掩盖问题。
相关问题
为什么不是 Execute 返回错误?
URL 过滤属于模板输出安全处理的一部分,危险值通常会以 #ZgotmplZ 进入结果,而不是作为普通业务错误返回。
把 javascript: 改成 URL 编码可以吗?
不应把编码当成安全修复。应该先决定动作是否真的需要脚本,再选择普通路由或固定动作映射。
template.URL 能不能接收数据库字段?
只有当数据库字段来自受控配置、经过明确协议和内容 allowlist,并且不会混入用户可控脚本时才应考虑;普通业务字段不应直接转换。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
226 收藏
-
282 收藏
-
482 收藏
-
253 收藏
-
441 收藏
-
128 收藏
-
251 收藏
-
475 收藏
-
231 收藏
-
329 收藏
-
470 收藏
-
Golang · Go问答 | 1小时前 | 标准库 · 错误处理 · go · 数据库 · Go database/sql sql.ErrNoRows QueryRow QueryRowContext186 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习