Go Goldmark Markdown 中的原始 HTML 为什么不输出
Go Goldmark Markdown 中的原始 HTML 不输出,通常不是 Markdown 写错了,而是 HTML renderer 的默认安全策略在生效。Goldmark 默认不渲染原始 HTML,也不会直接放行潜在危险链接;只有在输入已经可信、并且业务确实需要保留 HTML 标签时,才应通过 html.WithUnsafe() 明确打开这个能力。
官方地址:https://github.com/yuin/goldmark
- 原始 HTML 是否输出由 renderer 控制,单纯调整 Markdown 文本或扩展列表不会改变默认安全边界。
html.WithUnsafe()会让原始 HTML 和潜在危险链接按输入内容输出,不能对陌生用户提交的 Markdown 直接开启。- 受信任模板可以在
goldmark.WithRendererOptions中配置;不可信内容应继续保持默认设置,或先经过专门的 HTML 清洗器。
先确认:消失的是原始 HTML,不是普通 Markdown
我第一次遇到这个问题时,输入里同时放了一个加粗语法和一个原始 标签。转换结果中的加粗仍然存在,只有标签没有出现在 HTML 中,这个现象说明解析器并没有整体失效,问题点更靠近输出阶段。
package main
import (
"bytes"
"fmt"
"github.com/yuin/goldmark"
)
func main() {
source := []byte("**保留的 Markdown**\n\n原始 HTML")
var output bytes.Buffer
// 默认实例保留 Markdown 语法,但不会直接输出原始 HTML 标签。
if err := goldmark.Convert(source, &output); err != nil {
// 转换失败要保留错误,避免把空结果误判为 HTML 被过滤。
panic(err)
}
fmt.Print(output.String())
}
这个最小例子适合用来判断问题性质:如果 **保留的 Markdown** 正常变成 ,而 没有按原样出现,就不要先去改输入字符串,也不要误以为必须增加 GFM 扩展。

真正的开关在 HTML renderer 的安全默认值
Goldmark 官方文档把 html.WithUnsafe 定义为 renderer 选项:默认情况下,原始 HTML 和潜在危险链接不会被渲染;启用后才会按输入内容输出。也就是说,extension.GFM 可以补充表格、删除线、任务列表和自动链接等语法,但它不是“允许原始 HTML”的开关。
| 输入或配置 | 默认表现 | 是否解决原始 HTML 不输出 | 适用判断 |
|---|---|---|---|
| 普通 Markdown 语法 | 正常渲染 | 不需要解决 | 标题、段落、强调等内容 |
extension.GFM | 增加 GFM 语法 | 不是专用开关 | 表格、任务列表等扩展 |
| 默认 HTML renderer | 不输出原始 HTML | 是问题来源 | 不可信 Markdown 的安全默认值 |
html.WithUnsafe() | 按输入输出原始 HTML 和危险链接 | 能解决显示问题 | 仅限已经信任或已清洗的内容 |
因此,排查时我会先问一句:这段 Markdown 来自代码仓库内的模板,还是来自评论、工单、富文本编辑器或外部接口?前一种情况通常可以考虑打开 renderer 选项;后一种情况不能为了让标签显示就无条件放开。
受信任内容怎样显式开启原始 HTML
如果 Markdown 是随程序发布的文档、内部维护的模板,或者在进入转换流程前已经完成了可信来源控制,可以把选项放在 Goldmark 实例创建处。关键是使用 goldmark.WithRendererOptions 传入 HTML renderer 的选项,而不是给输入字符串加转义。
package main
import (
"bytes"
"fmt"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/renderer/html"
)
func main() {
source := []byte("**标题**\n\n已发布")
markdown := goldmark.New(
// 只对已确认可信的 Markdown 实例打开原始 HTML 输出。
goldmark.WithRendererOptions(html.WithUnsafe()),
)
var output bytes.Buffer
if err := markdown.Convert(source, &output); err != nil {
// 让上层能够区分转换错误和空内容,不能静默吞掉错误。
panic(err)
}
fmt.Print(output.String())
}
这个配置改变的是 renderer 的输出策略:输入中的 已发布 会保留为 HTML。若还需要 GFM 语法,应把 extension.GFM 放进 goldmark.WithExtensions;它和 html.WithUnsafe 负责的是两类不同问题。

不可信 Markdown 不要用 WithUnsafe 硬修
公开评论、用户昵称、工单描述和第三方同步内容都不能默认当作可信模板。WithUnsafe 的语义不是“只让安全的 span 显示”,而是把 renderer 的这层保护交给调用方承担。官方文档也建议对不可信内容使用 HTML sanitizer,例如 Bluemonday 一类的清洗方案。
更稳妥的分层方式是:
- 先按来源给 Markdown 标注可信级别,不要让所有请求共享一个全局开启了
WithUnsafe的 renderer。 - 确实需要保留少量标签时,先使用允许列表清洗,再将清洗后的结果交给转换流程。
- 把“原始 HTML 是否显示”和“HTML 是否允许哪些标签、属性、协议”拆成两个责任,不要用一个布尔配置代替策略。
- 对不可信内容继续使用默认 renderer;如果产品需要富文本,优先采用明确的受限语法或专用渲染方案。
// renderTrustedTemplate 只接收应用内维护的模板,不接收用户评论或外部同步内容。
func renderTrustedTemplate(source []byte) (string, error) {
markdown := goldmark.New(
// 可信模板允许保留必要的 HTML 标签;调用方必须保证输入来源受控。
goldmark.WithRendererOptions(html.WithUnsafe()),
)
var output bytes.Buffer
if err := markdown.Convert(source, &output); err != nil {
// 将转换错误返回给上层,由上层决定是否记录和回退。
return "", err
}
return output.String(), nil
}
这个函数名故意把 TrustedTemplate 写进边界里。它不是安全过滤器,也不能拿来处理用户输入;这样做的价值是让调用点更容易接受代码审查,避免未来把同一个 renderer 复用到评论接口。
发布前按三项边界检查配置
我会把最后的判断压缩成三项,而不是只检查页面上有没有显示标签:
- 配置边界:只有确实需要原始 HTML 的实例才传入
html.WithUnsafe(),默认实例保持默认值。 - 输入边界:确认内容来自版本化模板、内部文档或经过清洗的来源;评论、表单和外部同步内容不能直接归为可信。
- 输出边界:明确最终 HTML 会被谁消费;如果会进入浏览器页面,必须继续考虑标签、属性和 URL 协议的清洗责任。
如果只是想让 Markdown 里的链接、表格或删除线生效,优先配置对应的 Goldmark 扩展,不要直接打开 WithUnsafe。只有在“原始 HTML 本身就是模板能力”的需求成立,并且输入来源受控时,才使用上面的显式配置。
常见问题
为什么加了 extension.GFM,HTML 还是不显示?
因为 GFM 扩展负责一组 Markdown 语法,原始 HTML 的放行由 HTML renderer 的 WithUnsafe 选项负责,两者不是同一个开关。
WithUnsafe 只允许 span 和 class 吗?
不是。它代表按输入内容渲染原始 HTML 和潜在危险链接,不是针对某一个标签的允许列表。需要精细控制时,应在转换前使用 HTML 清洗策略。
把 HTML 标签写成 Markdown 转义形式可以吗?
转义只能让标签以文字显示,不能达到保留 HTML 结构的目的。如果目标只是展示标签文本,转义是正确选择;如果目标是渲染标签,则需要重新评估输入信任级别和 renderer 配置。
应不应该全局复用开启 WithUnsafe 的 Goldmark 实例?
不建议把它作为所有入口的默认实例。更清楚的做法是为可信模板单独封装实例,普通用户输入继续使用默认 renderer,并在代码层面让两条路径有明显名称差异。
一句话总结:Goldmark 原始 HTML 不输出是默认安全边界,不是解析失败;用 html.WithUnsafe() 可以解决受信任模板的显示问题,但不应跳过输入来源和最终 HTML 消费场景的判断。
Go Goldmark 怎么遍历 AST 提取标题目录
- 上一篇
- Go Goldmark 怎么遍历 AST 提取标题目录
- 下一篇
- Go Goldmark 怎么为自定义节点实现 HTML 渲染
-
- Golang · Go问答 | 25分钟前 | 缓存 · go · Go Ristretto NumCounters MaxCost
- Go Ristretto NumCounters 和 MaxCost 分别约束什么
- 159浏览 收藏
-
- Golang · Go问答 | 26分钟前 |
- Go Ristretto Set 返回后立刻 Get 为什么可能取不到
- 395浏览 收藏
-
- Golang · Go问答 | 27分钟前 |
- Go Ristretto 缓存未满时条目为什么也可能不被接纳
- 359浏览 收藏
-
- Golang · Go问答 | 36分钟前 |
- Go Badger 迭代器不关闭为什么会影响资源释放
- 193浏览 收藏
-
- Golang · Go问答 | 38分钟前 |
- Go Badger ValueCopy 和 Value 回调的生命周期有什么区别
- 159浏览 收藏
-
- Golang · Go问答 | 39分钟前 |
- Go Badger 事务提交报冲突时应该怎样重新执行
- 234浏览 收藏
-
- Golang · Go问答 | 49分钟前 | 故障排查 · Go问答 · ErrTimeout Go bbolt 数据库文件锁 Open Timeout
- Go bbolt 数据库已被占用时怎么避免 Open 无限等待
- 411浏览 收藏
-
- Golang · Go问答 | 52分钟前 |
- Go bbolt 事务结束后保存的值切片为什么不可靠
- 224浏览 收藏
-
- Golang · Go问答 | 1小时前 |
- Go Participle 增加前瞻和重写歧义语法有什么区别
- 426浏览 收藏
-
- Golang · Go问答 | 1小时前 | Go问答 · Go 注释 ast 语法树 Participle Elide
- Go Participle 被 Elide 的注释为何不出现在语法树
- 198浏览 收藏
-
- Golang · Go问答 | 1小时前 | go · Go 递归下降解析器 Participle 左递归
- Go Participle 左递归语法为什么无法直接解析
- 240浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 460次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 522次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 540次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 488次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 307次使用
-
- glow工具在命令行读取Markdown好物分享
- 2022-12-29 133浏览
-
- Go 问答:为什么并发读写 map 会 panic,sync.Map 和锁该怎么选
- 2026-06-12 109浏览
-
- Go 问答:defer 为什么不适合直接放在大循环里,资源该怎么释放
- 2026-06-12 418浏览
-
- Go 问答:为什么接口变量明明装的是 nil,判断却不等于 nil
- 2026-06-13 238浏览
-
- Go 问答:append 后原 slice 为什么有时会变,有时不会
- 2026-06-13 236浏览

