模块脚本受csp严格约束:内联模块需nonce或hash授权,动态import需'strict-dynamic'配合初始授信;禁用'unsafe-eval',避免仅配'self'导致cdn模块加载失败,且标签不支持strict-dynamic与nonce。

模块脚本(<script type="module"></script>)本身不触发内联执行风险,但其加载和执行仍受 CSP 严格约束——关键在于 script-src 指令是否允许模块来源,以及是否误配 'unsafe-eval' 或遗漏 'strict-dynamic' 等现代机制。
模块脚本的CSP行为特点
浏览器对模块脚本的处理与传统脚本不同:
- 模块脚本默认是“外部化”的,即使写在 HTML 中(如
<script type="module" src="app.js"></script>),也不算内联脚本,不触发'unsafe-inline'检查 - 但若使用内联模块(
<script type="module">import('./a.js');</script>),它仍需满足script-src策略,且必须显式允许——此时不能靠'unsafe-inline',而应使用nonce-或hash-方式授权 - 模块的动态导入(
import())属于运行时脚本加载,受script-src+'strict-dynamic'联合控制;没有'strict-dynamic'时,每次import()的目标 URL 都需单独列入白名单
适配模块脚本的推荐CSP配置
针对现代前端项目(含 Vite、Webpack 5+、ESM 构建产物),建议采用以下最小可行策略:
-
基础加载:用
'self'允许同源模块,配合明确的 CDN 域名(如https://cdn.example.com) -
动态导入支持:添加
'strict-dynamic',使已授信的模块可自主加载子模块,避免无限追加 URL 白名单 -
内联模块授权:若必须用内联模块,生成唯一
nonce并注入响应头与标签:Header always set Content-Security-Policy "script-src 'self' 'strict-dynamic' 'nonce-{随机值}';"
对应 HTML:<script type="module" nonce="{同一随机值}">...</script> -
禁用 eval 类风险:模块环境通常无需
'unsafe-eval';除非用了new Function()或eval(),否则应坚决移除
常见陷阱与绕过场景
看似合规的配置,可能因细节失效:
- 只写
script-src 'self',但模块依赖的第三方库(如 CDN 上的lodash-es)来自不同域名 → 加载失败或被拦截 - 启用了
'strict-dynamic'却没配'self'或初始入口的nonce→ 动态导入全部被拒('strict-dynamic'不继承父源,只信任已授信脚本的调用链) - 用
<meta>标签设置 CSP → 对模块脚本无效,因为<meta>不支持strict-dynamic和nonce,且无法覆盖 404/500 响应 - Apache/Nginx 配置中漏掉
Header always set→ 错误页无 CSP,攻击者可诱导用户访问不存在路径,再注入恶意模块加载逻辑
与 sockjs-client 等兼容性工具的协同
当项目同时使用模块脚本和旧式实时通信库(如 sockjs-client)时,需分层处理:
- 模块主应用走严格策略:
script-src 'self' 'strict-dynamic' 'nonce-xxx' - sockjs-client 若走 XHR 或 EventSource,只需确保
connect-src放行其目标地址;若用 JSONP 回退,则必须额外允许对应 script 来源(不推荐) - WebSocket 传输完全绕过 script-src 限制,但需确认
connect-src已包含wss:地址 —— 这是更干净的解耦方式 - 避免为兼容 sockjs-client 而全局放宽
script-src(如加'unsafe-inline'),应通过nonce或独立子路径策略隔离风险面











