Trusted Types实战指南:防御XSS攻击的前端安全利器
又踩坑了,Trusted Types 报错把我拦在了 CSP 门外
上周上线一个新功能,本地跑得好好的,一部署到测试环境,控制台直接炸了:Uncaught TypeError: Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment.。我一脸懵,这啥玩意?我明明只是动态插了个 HTML 字符串进去,怎么突然就不行了?
查了下,原来是项目里启用了 CSP(Content Security Policy),而且加了 require-trusted-types-for 'script' 这条策略。之前只知道 CSP 能防 XSS,但没细看过 Trusted Types 这块,结果栽了。
折腾了半天,试了三种“野路子”
一开始我以为是 innerHTML 不能用,赶紧换成 createElement + appendChild 手动拼 DOM。改完发现,虽然不报错了,但性能差得离谱——那段 HTML 是从接口拿的模板,嵌套好几层,手动拼得写几十行代码,还容易出错。放弃。
然后我想到:是不是可以用 DOMPurify 先清洗一下?于是装了库,把内容过一遍再塞进 innerHTML。结果还是报同样的错。后来才明白,CSP 不是看内容干不干净,而是看你有没有“可信来源”。就算你传的是 “hello“,只要不是 Trusted Type 对象,浏览器照样拒绝。
最离谱的是,我还试过用 dangerouslySetInnerHTML(React 项目),心想 React 应该有内部处理吧?结果照样报错。看来 CSP 这层是在浏览器底层拦的,框架也救不了。
核心代码就这几行:创建自己的 Trusted Type 策略
翻了 MDN 和 Google 的文档,终于搞明白:Trusted Types 要求你显式地“信任”某些字符串。你需要先注册一个策略(Policy),然后用这个策略去“包装”你的 HTML 字符串,生成一个 TrustedHTML 对象,再赋值给 innerHTML 才行。
关键点来了:策略必须在页面加载早期就注册,最好在所有 JS 执行前。所以我把它扔到了 HTML 的头部,用一个内联脚本搞定:
<script>
if (window.trustedTypes && window.trustedTypes.createPolicy) {
window.trustedTypes.createPolicy('default', {
createHTML: (string, sink) => string,
createScriptURL: (string, sink) => string,
createScript: (string, sink) => string
});
}
</script>
这里我踩了个坑:一开始只写了 createHTML,结果后面有个地方用了 document.write(历史遗留代码,别问),又报错了。后来干脆三个都加上,图个省事。注意,sink 参数是可选的,但建议保留,未来可能用于更细粒度的控制。
不过,这样写其实有风险——等于把所有字符串都无条件信任了。理想情况下,你应该在 createHTML 里做校验或清洗。比如结合 DOMPurify:
if (window.trustedTypes && window.trustedTypes.createPolicy) {
window.trustedTypes.createPolicy('myPolicy', {
createHTML: (string) => {
// 假设 DOMPurify 已经加载
return DOMPurify.sanitize(string, { RETURN_TRUSTED_TYPE: true });
}
});
}
但问题来了:DOMPurify 得在策略之后加载,而策略又得尽早注册。所以我最后还是先用最简方案上线,回头再重构。毕竟业务要赶,安全也不能停,先保证能跑再说。
踩坑提醒:这三点一定注意
- 策略名不能乱起:我一开始用
'allowAll',结果 Chrome 控制台警告说这个名字可能被滥用,建议用项目相关的命名。后来改成'jztheme-sanitizer'(仅示例,实际项目用自己名字)。 - 别在策略里 throw Error:如果
createHTML里抛异常,整个赋值会失败,而且错误信息很模糊。我调试时不小心在里面加了 console.log,结果忘了删,线上直接白屏。 - 第三方库可能不兼容:比如我们用的某个富文本编辑器,内部大量使用 innerHTML,但没考虑 Trusted Types。临时方案是给它打 patch,或者在策略里对特定 sink 做放行。长远看,得等库作者更新。
另外,如果你用的是现代框架(比如 React 18+),其实它们已经部分支持 Trusted Types 了。但像我们这种混合老项目,还得自己兜底。
后来试了下发现:不是所有 innerHTML 都需要改
有意思的是,我发现只有那些**动态拼接用户输入**的地方才需要处理。比如从接口返回的、带变量的模板字符串。如果是纯静态的 HTML 片段(比如 “
“),其实可以不用走策略——但为了统一管理,我还是全包了,避免以后有人不小心引入漏洞。
还有一个细节:Trusted Types 只在启用了 CSP 的环境下生效。所以本地开发时完全没问题,只有上了测试/生产环境才暴露。这也导致问题很难复现,得专门配一个带 CSP 的本地服务器才能调试。我后来用 nginx 搞了个本地代理,加了 CSP 头,才方便测试。
改完后还有一两个小问题,但无大碍
目前方案上线后,主流程没问题了。但偶尔在 Safari 上看到警告(Safari 对 Trusted Types 支持还不完善),不过不影响功能。另外,如果用户禁用了 JS,CSP 本身也不会生效,所以这块其实只影响现代浏览器下的安全加固。
说到底,Trusted Types 是个好东西,能从根本上堵住 DOM-based XSS。但对现有项目来说,改造成本不小,尤其是那些重度依赖 innerHTML 的老代码。我的建议是:新项目直接启用,老项目逐步迁移,优先处理高风险模块。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。比如有没有自动化的工具能扫描出所有需要 Trusted Types 的地方?或者有没有更优雅的策略管理方式?我还在摸索中。

暂无评论