nonce必须由服务端动态生成,硬编码、复用或前端生成均属高危;应使用crypto.randomBytes等密码学安全随机源生成,并确保响应头与HTML中nonce值字符级一致,避免CDN缓存、编码符号未清洗等问题。

nonce 必须由服务端每次请求动态生成
硬编码、复用、前端生成的 nonce 值在安全审计中直接判为高危——它等于没开 CSP。浏览器不报错,但攻击者可重放或预测值绕过防护。
正确做法是:服务端响应前调用密码学安全随机源生成,例如 Node.js 中:
const nonce = crypto.randomBytes(32).toString('hex');
注意避开这些坑:
-
Math.random()、new Date().getTime()—— 可预测,审计工具会直接标红 -
crypto.randomBytes(16).toString('base64')含+、/、=—— 需.replace(/[+/=]/g, '')清洗,否则 HTML 解析失败 - CDN 缓存了带
nonce的 HTML 模板 —— 同一值多次下发,失去“一次性”意义,审计项“nonce 重复使用”命中
响应头与 script 标签的 nonce 值必须字符级一致
浏览器静默校验,不报错也不警告,脚本直接被拦。审计时会比对响应头和 HTML 源码,任何微小差异都算失效。
立即学习“前端免费学习笔记(深入)”;
常见不一致场景:
- 响应头写
script-src 'nonce-abc123 '(末尾空格),HTML 里是nonce="abc123" - 大小写混用:
nonce="Abc123"vs'nonce-abc123' - 模板插值未转义,
nonce值含&或双引号,导致<script nonce="...标签提前截断 - 把 CSP 写进
<meta http-equiv=" content-security-policy> —— 该方式<strong>完全不支持 nonce,审计必挂</script>
框架内联脚本(Webpack/Vite/React SSR)必须显式注入 nonce
构建工具自动注入的 runtime 脚本(如 Webpack 的 __webpack_require__、Vite 的 HMR 初始化逻辑)默认不带 nonce,一旦 CSP 启用,页面白屏或功能异常,审计会标记“第三方脚本未授权执行”。
各框架处理要点:
- Webpack:入口 JS 执行前设全局变量
__webpack_nonce__ = nonce - Vite:在
vite.config.ts中配置build.rollupOptions.output.manualChunks,并确保内联 runtime 脚本通过模板注入nonce - React SSR:将
nonce作为 context 或 props 注入根组件,手写<script nonce={nonce}>,不能依赖 hydration 补充 - Vue SSR:用
renderToString(app, { nonce })传入,模板中插值{{ nonce }},确认模板引擎不转义引号和特殊字符
strict-dynamic 和 nonce 必须共存且顺序合规
只配 nonce 不够——它只放行初始内联脚本;后续用 document.createElement('script') 动态创建的脚本仍被拦。审计要求必须支持合法的动态加载行为,否则业务功能无法落地。
关键约束:
-
script-src中必须同时出现'nonce-xxx'和'strict-dynamic',且'nonce-xxx'在前 - 首个执行的脚本(通常是首屏初始化脚本)必须带该 nonce,否则
strict-dynamic不生效 -
script-src 'self' 'strict-dynamic'是错误写法:'self'会让浏览器忽略'strict-dynamic' -
onclick等内联事件仍被拦截,必须改用addEventListener,这点常被审计忽略但实际存在风险
真正卡住落地的,往往不是生成逻辑,而是三处 nonce 值(响应头、HTML 标签、框架 runtime)之间毫秒级的同步脱节,以及模板引擎对特殊字符的静默截断——这些细节在自动化扫描里藏得深,但人工审计一眼就能揪出。



















