跨站脚本攻击(XSS)连续 15 年稳居 OWASP Top 10,而 DOM XSS 是其中最难防御的变种——攻击代码从未经过服务端,直接在浏览器 DOM 中触发。2024 年 Chrome 123 开始对未使用 Trusted Types 的页面发出控制台警告,2026 年初 Chrome 已在高安全级别下强制执行 Trusted Types 策略。如果你的前端代码还在直接操作 innerHTML,是时候了解一下这个浏览器原生的安全防线了。
🔐 一、DOM XSS 的本质与 Trusted Types 的解法
1.1 为什么 DOM XSS 如此难防?
传统 XSS(反射型、存储型)可以通过服务端转义来防御,但 DOM XSS 的攻击路径完全不同:恶意数据从 URL hash、document.referrer、postMessage 等客户端来源直接进入 DOM 操作函数,服务端根本看不到这段数据。
最常见的 DOM XSS 触发点:
// ❌ 危险写法:直接将用户输入插入 DOM
element.innerHTML = location.hash.slice(1); // URL 片段注入
element.innerHTML = localStorage.getItem('name'); // 存储型 DOM XSS
document.write(new URLSearchParams(location.search).get('q')); // 查询参数注入
根据 Google 的统计数据,超过 60% 的 XSS 漏洞源于 innerHTML 和 document.write 的不安全使用。传统的 CSP(Content Security Policy)可以限制脚本来源,但无法阻止这些 DOM API 被滥用——因为恶意代码是通过合法的 JavaScript 执行的。
1.2 Trusted Types 的核心思想
Trusted Types 的解法非常优雅:不禁止危险 API,而是要求所有传入的数据必须经过「可信策略」处理后才能使用。
具体来说,它做了三件事:
- 创建安全策略(Policy):定义一个数据处理函数,所有数据必须经过这个函数才能变成「可信类型」
- 包装危险 API:
innerHTML、outerHTML、document.write等 API 被重写,只接受 TrustedHTML 类型的数据 - 强制执行(Enforcement):通过 CSP 头
require-trusted-types-for 'script'启用强制模式,违规直接报错
💡 **提示:**Trusted Types 不是一个 npm 库,而是浏览器原生 API(Chrome、Edge 已全面支持,Firefox 和 Safari 正在实现中)。它不需要任何运行时依赖。
1.3 类型体系
Trusted Types 定义了三种可信类型,分别对应不同的 DOM 操作:
| 类型 | 对应 API | 用途 |
|---|---|---|
TrustedHTML |
innerHTML, outerHTML, insertAdjacentHTML |
HTML 片段插入 |
TrustedScript |
script.src, script.text, eval() |
脚本执行 |
TrustedScriptURL |
script.src, iframe.src, fetch() |
URL 加载 |
⚡ **关键结论:**Trusted Types 的核心理念是「类型即安全」——通过类型系统在运行时强制执行数据清洗,让
innerHTML注入从「可能忘记转义」变成「不转义就报错」。
🚀 二、从零实现:Trusted Types 策略实战
2.1 基础策略创建
下面是一个完整的 Trusted Types 策略实现,包含 HTML 转义和 URL 校验:
// 创建 Trusted Types 策略:统一处理所有 DOM 插入的数据
const securityPolicy = trustedTypes.createPolicy('default', {
// 处理 HTML 插入:转义所有特殊字符
createHTML(input) {
const div = document.createElement('div');
div.textContent = String(input); // textContent 自动转义 HTML 实体
return div.innerHTML;
},
// 处理脚本:只允许已知的安全脚本
createScript(input) {
const allowed = ['console.log', 'window.location.reload'];
const str = String(input);
if (!allowed.some(fn => str.startsWith(fn))) {
throw new Error(`🚫 脚本不在白名单中: ${str.slice(0, 50)}`);
}
return str;
},
// 处理脚本 URL:只允许同源和可信域名
createScriptURL(input) {
const url = new URL(String(input), location.origin);
const trustedHosts = ['cdn.example.com', 'apis.google.com', location.hostname];
if (!trustedHosts.includes(url.hostname)) {
throw new Error(`🚫 不可信的脚本源: ${url.hostname}`);
}
return url.href;
}
});
// ✅ 安全使用:通过策略创建可信类型
const userInput = '<img src=x onerror=alert("XSS")>';
document.getElementById('content').innerHTML = securityPolicy.createHTML(userInput);
// 输出: <img src=x onerror=alert("XSS")> (已转义,不会执行)
2.2 多策略架构:按功能分离职责
生产环境中,不应该把所有逻辑塞进一个策略。推荐按功能模块创建独立策略:
// 模板渲染策略:处理 UI 模板中的动态数据
const templatePolicy = trustedTypes.createPolicy('template', {
createHTML(input) {
// 允许安全的 HTML 标签,过滤危险属性
return sanitizeHTML(String(input), {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'span', 'p', 'br'],
allowedAttributes: {
'a': ['href', 'title'],
'span': ['class'],
},
// 禁止所有 on* 事件处理器
disallowedAttributesMode: 'discard',
});
}
});
// 动态脚本策略:用于第三方分析和 A/B 测试
const analyticsPolicy = trustedTypes.createPolicy('analytics', {
createScriptURL(input) {
const url = new URL(String(input), location.origin);
// 只允许 Google Analytics 和自研埋点服务
const allowed = ['google-analytics.com', 'analytics.example.com'];
if (!allowed.some(host => url.hostname.endsWith(host))) {
throw new Error(`🚫 不允许的分析脚本: ${url.hostname}`);
}
return url.href;
}
});
// 富文本编辑器策略:允许更多 HTML 标签
const editorPolicy = trustedTypes.createPolicy('rich-editor', {
createHTML(input) {
return sanitizeHTML(String(input), {
allowedTags: ['b', 'i', 'u', 'h1', 'h2', 'h3', 'p', 'ul', 'ol', 'li',
'blockquote', 'code', 'pre', 'a', 'img', 'table', 'tr', 'td', 'th'],
allowedAttributes: {
'a': ['href', 'title', 'target'],
'img': ['src', 'alt', 'width', 'height'],
'code': ['class'],
},
});
}
});
⚠️ **警告:**每个策略名称只能注册一次。如果两个模块使用相同名称创建策略,第二个会抛出
TypeError。建议用模块名作为策略名称前缀。
2.3 与 DOMPurify 集成
DOMPurify 是最流行的 HTML 清理库,它已经内置了 Trusted Types 支持:
// DOMPurify + Trusted Types:生产级 HTML 清理方案
import DOMPurify from 'dompurify';
// 创建与 DOMPurify 集成的策略
const purifyPolicy = trustedTypes.createPolicy('dompurify', {
createHTML(dirty) {
// DOMPurify 返回清理后的 HTML 字符串
// 返回值自动被包装为 TrustedHTML 类型
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'title', 'target', 'class'],
ALLOW_DATA_ATTR: false,
// 防止 CSS 注入
FORBID_ATTR: ['style'],
// 防止 DOM Clobbering
SANITIZE_DOM: true,
});
}
});
// 使用示例
function renderUserComment(html) {
// ✅ 安全:经过 DOMPurify 清理 + Trusted Types 类型保证
commentContainer.innerHTML = purifyPolicy.createHTML(html);
}
// ❌ 在强制模式下,直接赋值会抛出 TypeError
// commentContainer.innerHTML = '<script>alert("XSS")</script>';
// TypeError: Failed to set the 'innerHTML' property: This document requires 'TrustedHTML' assignment.
📌 **记住:**DOMPurify 的
RETURN_TRUSTED_TYPE选项可以让它直接返回TrustedHTML,但为了更好的审计和调试,推荐显式创建策略并在策略中调用 DOMPurify。
💡 三、生产部署:CSP 配置与渐进式迁移
3.1 CSP 头配置
Trusted Types 的强制执行通过 CSP 头控制。以下是一个完整的生产级配置:
# Nginx 配置示例
# 阶段 1:报告模式(不阻断,只报告违规)
add_header Content-Security-Policy "
require-trusted-types-for 'script';
trusted-types default template analytics dompurify;
report-uri /csp-violation-report;
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
";
# 阶段 2:强制模式(阻断所有违规)
# 以下配置在迁移完成后启用
add_header Content-Security-Policy "
require-trusted-types-for 'script';
trusted-types default template analytics dompurify;
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
";
CSP 指令说明:
| 指令 | 作用 | 示例 |
|---|---|---|
require-trusted-types-for 'script' |
启用强制模式,危险 API 必须使用可信类型 | 'script' 是目前唯一支持的值 |
trusted-types |
声明允许的策略名称列表 | default template analytics |
trusted-types 'allow-duplicates' |
允许同名策略重复创建 | 第三方库兼容 |
report-uri |
违规报告地址(报告模式用) | /csp-violation-report |
⚡ **关键结论:**迁移分为三个阶段——报告模式收集违规、逐个修复代码、最终切换到强制模式。不要试图一步到位,大型项目通常需要 2-4 周完成迁移。
3.2 渐进式迁移策略
对于已有的大型项目,推荐以下迁移路径:
阶段 1:审计与报告(1 周)
// 安装报告处理器,收集所有违规
// server.js — Express 违规报告端点
app.post('/csp-violation-report', express.json(), (req, res) => {
const violation = req.body['csp-report'];
console.warn('🚨 CSP Violation:', {
blocked: violation['blocked-uri'],
violated: violation['violated-directive'],
source: violation['source-file'],
line: violation['line-number'],
});
// 存储到数据库,分析高频违规点
saveViolation(violation);
res.status(204).end();
});
阶段 2:创建默认策略兜底(2-3 天)
// 在应用入口文件最顶部创建默认策略
// ⚠️ 必须在所有其他代码之前执行
if (typeof trustedTypes !== 'undefined') {
trustedTypes.createPolicy('default', {
createHTML(input) {
// 默认策略:记录违规但不阻断
console.warn('⚠️ 未通过策略处理的 HTML:', String(input).slice(0, 100));
const div = document.createElement('div');
div.textContent = String(input);
return div.innerHTML;
},
createScript(input) {
console.warn('⚠️ 未通过策略处理的 Script:', String(input).slice(0, 100));
return String(input);
},
createScriptURL(input) {
console.warn('⚠️ 未通过策略处理的 ScriptURL:', String(input).slice(0, 100));
return String(input);
}
});
}
阶段 3:逐模块替换(1-3 周)
// 逐个模块替换 innerHTML 为安全策略
// ❌ 迁移前
function renderUserProfile(user) {
profileEl.innerHTML = `
<h2>${user.name}</h2>
<p>${user.bio}</p>
<a href="${user.website}">个人网站</a>
`;
}
// ✅ 迁移后
const profilePolicy = trustedTypes.createPolicy('user-profile', {
createHTML(input) {
return DOMPurify.sanitize(String(input), {
ALLOWED_TAGS: ['h2', 'p', 'a', 'span'],
ALLOWED_ATTR: ['href'],
});
}
});
function renderUserProfile(user) {
profileEl.innerHTML = profilePolicy.createHTML(`
<h2>${escapeHtml(user.name)}</h2>
<p>${escapeHtml(user.bio)}</p>
<a href="${escapeUrl(user.website)}">个人网站</a>
`);
}
3.3 框架集成
现代前端框架已经部分内置了 XSS 防护,但仍有绕过风险。以下是各框架的最佳实践:
Vue 3 + Trusted Types:
// vue-trusted-html.js — Vue 自定义指令
// 用于替代 v-html,强制经过 Trusted Types 处理
import { createPolicy } from './trusted-types-setup';
const vueHtmlPolicy = createPolicy('vue-html', {
createHTML(input) {
return DOMPurify.sanitize(input);
}
});
export const vTrustedHtml = {
mounted(el, binding) {
el.innerHTML = vueHtmlPolicy.createHTML(binding.value);
},
updated(el, binding) {
if (binding.value !== binding.oldValue) {
el.innerHTML = vueHtmlPolicy.createHTML(binding.value);
}
}
};
// 使用:<div v-trusted-html="userContent"></div>
React 中的 Trusted Types:
// React 本身对 dangerouslySetInnerHTML 有警告
// 结合 Trusted Types 可以做到运行时强制防护
const trustedHtmlPolicy = trustedTypes.createPolicy('react-html', {
createHTML(input) {
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target'],
});
}
});
function SafeRichText({ content }) {
// ✅ 通过策略处理后的 HTML
return (
<div
dangerouslySetInnerHTML={{
__html: trustedHtmlPolicy.createHTML(content)
}}
/>
);
}
// ❌ 直接使用会触发 CSP 违规
// <div dangerouslySetInnerHTML={{ __html: userInput }} />
🔧 四、常见坑点与避坑指南
4.1 第三方库兼容性问题
最大的痛点是第三方库可能直接操作 innerHTML。解决方案:
// 在加载第三方库之前创建默认策略
// 这样第三方库的 innerHTML 操作会经过你的策略处理
trustedTypes.createPolicy('third-party-default', {
createHTML(input) {
// 对第三方库的 HTML 做基本清理
return DOMPurify.sanitize(String(input), {
// 根据第三方库的需求调整白名单
ADD_TAGS: ['svg', 'path', 'use'], // 图标库需要
ADD_ATTR: ['viewBox', 'd', 'fill'], // SVG 属性
});
}
});
4.2 调试技巧
// 开发环境下注册违规监听器
if (process.env.NODE_ENV === 'development') {
document.addEventListener('securitypolicyviolation', (e) => {
console.group('🔒 Trusted Types Violation');
console.error('Blocked:', e.blockedURI);
console.error('Directive:', e.violatedDirective);
console.error('Source:', e.sourceFile, `:${e.lineNumber}`);
console.error('Original:', e.originalPolicy);
console.groupEnd();
});
}
4.3 性能影响
| 场景 | 无 Trusted Types | 有 Trusted Types | 开销 |
|---|---|---|---|
| 简单文本插入 | 0.001ms | 0.003ms | +0.002ms |
| 复杂 HTML(1KB) | 0.05ms | 0.12ms | +0.07ms |
| DOMPurify 清理 | 0.8ms | 0.85ms | +0.05ms |
| 1000 次批量插入 | 45ms | 52ms | +15% |
⚠️ **警告:**Trusted Types 的性能开销在绝大多数场景下可以忽略。唯一需要注意的是在高频动画或
requestAnimationFrame循环中避免使用策略函数——这种场景应该预先处理好数据。
✅ 总结与建议
Trusted Types 不是银弹,但它是目前唯一能在运行时强制阻止 DOM XSS 的浏览器原生方案。相比传统的「代码审查 + ESLint 规则」,它的优势在于:即使开发者犯了错误,CSP 强制模式也会直接阻断注入。
实施建议:
- ✅ 新项目从第一天就启用 Trusted Types + CSP 强制模式
- ✅ 已有项目先用报告模式收集数据,再渐进式迁移
- ✅ 将 Trusted Types 策略与 DOMPurify 结合使用
- ✅ 用
securitypolicyviolation事件做开发期调试 - ❌ 不要创建过于宽松的默认策略(失去了防护意义)
- ❌ 不要忽略第三方库的兼容性问题
相关资源:
- 📄 Trusted Types W3C 规范
- 🔧 DOMPurify — 最流行的 HTML 清理库
- 🛡️ CSP Evaluator — Google 的 CSP 配置检查工具
- 📊 OWASP DOM XSS Prevention Cheat Sheet
📌 **记住:**安全不是一次性工程。Trusted Types + CSP + DOMPurify 组成了前端 XSS 防御的三道防线,缺一不可。定期审计 CSP 违规报告,持续迭代安全策略,才是真正的生产级安全实践。