前端安全边界一、导读1 怎么读这篇笔记定义先讲「威胁从哪里来、浏览器默认帮你挡了什么」再讲你该怎么写代码才不拆墙。代码块含// 语法要点、//正确示例、//错误示例安全话题也要能落地到代码而不是口号集合。啥时候用每个小节后列「联调、上线、接入第三方脚本」常见触点。与前文的关系你在《前端网络层》里写过 Cookie 与 CORS在《浏览器存储》里写过 HttpOnly、SameSite本篇把它们放进统一威胁模型里读而不是三条孤立知识点。目标能说清XSS / CSRF / 点击劫持 / 敏感信息落点四类主线的原理边界能在评审里指出「这句 innerHTML 为什么危险、这个登录态为什么不该进 localStorage、这段 CORS 配置到底方便了谁」。免责声明安全是系统级问题前端守住的是输入输出与最小暴露面。任何「靠前端加密密钥」的方案都不成立——因为浏览器里的字符串用户都能看见。2 威胁建模速记STRIDE知道一张表就够定义STRIDE是微软经典威胁分类法用以穷举思考攻击面不是前端独舞但产品评审时很好用字母含义前端常触点SSpoofing 伪装冒充用户/站点会话窃取、钓鱼页 UITTampering 篡改改数据、改包无完整性校验的静态资源、被劫持 CDNRRepudiation 抵赖否认操作审计日志应在服务端IInformation Disclosure 泄露不该看的被看了XSS、Referer、错误栈上传DDenial of Service 拒绝服务拖垮可用性主线程炸弹、无限弹窗体验/可用性EElevation of Privilege 提权低权限变高权限越权接口、IDOR本篇不展开每一条的攻防全书但你在 PR 描述里写一句「此项改动影响 STRIDE 的 I/T」——评审档次立刻不一样。二、先搭「威胁模型」我们在防谁1 资产、威胁、暴露面定义资产— 用户数据、会话、钱、声誉。威胁— 窃取会话、伪造请求、钓鱼、植入恶意脚本、拖库多在后端。暴露面— 浏览器能读写的所有入口HTML拼接、postMessage、URL 参数、第三方脚本、存储 API。前端的目标不是「绝对不被黑」而是提高攻击成本、避免低级失误把大门敞开。// 语法要点凡是「把不可信字符串塞进能执行的环境」都是高危//错误示例element.innerHTMLuserInput;//正确示例——先问这是纯文本还是富文本纯文本就用 textContentelement.textContentuserInput;啥时候用设计阶段先列数据从哪来、到哪去——再选渲染方式。2 「同源策略」与「不是万能的」定义同源策略限制不同源的页面读对方 DOM / 默认不读响应体——所以才有CORS。但它挡不住同源下的XSS脚本已经“成为页面的一部分”。CSRF浏览器会自动带 Cookie。点击劫持视觉欺骗。供应链投毒你主动import了坏包。// 语法要点CORS 是放松读权限不是「鉴权」//错误示例——以为「开 CORS」就等于安全接口// Access-Control-Allow-Origin: * 只会让浏览器允许页面读响应攻击者自有办法从用户浏览器发请求//正确示例——身份校验仍靠 Cookie CSRF 防御 / Token 正确存储策略或后端会话体系啥时候用评审后端 CORS— 问我们到底允许哪些源读有没有credentials三、XSS跨站脚本与「可执行上下文」1 反射型 / 存储型 / DOM 型记语义定义简化教学版反射型— 恶意输入立刻从 URL 等弹回页面常用于钓鱼链接。存储型— 恶意内容进了数据库每次打开页面都会执行。DOM 型— 纯前端路由把不可信数据写进 DOM /eval不经由后端存储也能出事。共同点不信任的数据进了可执行或可被解析为 HTML的通道。// 语法要点危险的「sink」 innerHTML、outerHTML、insertAdjacentHTML、document.write、eval、new Function、setTimeout(字符串)、URL 的 javascript: 协议 href//错误示例div.innerHTMLp${name}/p;// name 含 img srcx onerror... 即炸//正确示例——模板引擎默认转义 CSP啥时候用评论、昵称、搜索词回显— 预设它就是坏的。2 输出编码与「上下文相关」定义HTML 转义不等价于JavaScript 字符串转义、也不等价于URL 编码。在HTML 文本节点里要 在属性里要注意引号闭合在javascript:URL里几乎一切都是毒。输出上下文典型坑原则性做法HTML 文本节点触发标签textContent或模板引擎 HTML-escapeHTML 属性值双引号... onload...断引号属性实体转义 能用布尔/严格枚举就别字符串裸插URLhref/srcjavascript:、data:text/htmlhttp(s)白名单URLAPI 解析禁止javascript:CSSstyle/style属性expression()、-moz-binding历史尽量不动态拼完整样式字符串用类名切换JSON进script typeapplication/json/script断出有脚本按 JSON 规则转义并由后端生成切勿字符串拼接一条总原则任何「把不可信数据」写进能产生执行语义的位置都是高危 sink先定上下文再谈转义表——不要幻想「统一escapeHtml一把梭」。//正确示例——把用户输入当作**文本节点**constpdocument.createElement(p);p.textContentuserName;root.append(p);//错误示例——模板字符串拼 hrefa.hrefjavascript:alert(1);// 若 user 可控灾难//正确示例——http(s) 白名单校验后再赋值functionsafeHref(url){try{constunewURL(url,location.href);if(u.protocolhttps:||u.protocolhttp:)returnu.href;}catch{/* ignore */}returnabout:blank;}啥时候用富文本编辑器— 需要HTML 消毒服务器侧为主客户端可第二层而不是「禁止用户输」这种鸵鸟策略。3 CSP内容安全策略从「补洞」到「限权」定义Content-Security-Policy响应头告诉浏览器哪些源可以执行脚本、加载图片、连接接口。典型default-src selfscript-src self拒绝内联脚本除非nonce/hashobject-src nonebase-uri selfframe-ancestors none或具体列表防嵌套点击劫持与 X-Frame-Options 协同// 语法要点响应头示意不是 JS Content-Security-Policy: default-src self; img-src self data:; connect-src self https://api.example.com; frame-ancestors none; base-uri self;//正确示例——静态站点先上「报告模式」观察误拦// Content-Security-Policy-Report-Only: ...//错误示例——script-src 写 unsafe-inline 还自以为「上了 CSP」// 内联脚本仍可执行XSS 仍快乐啥时候用上线前— 与后端协同下发 CSP先从 Report-Only 收集误杀。4upgrade-insecure-requests与block-all-mixed-content定义在「暂时还有个别 http 子资源」的迁移期可用 CSPupgrade-insecure-requests自动把http://资源请求升级为https://若服务端不存在会 404但避免混内容被动攻击面。block-all-mixed-content或等价策略直接拦混合内容更硬。精确注意这不替代你在资源 URL 上修正确链接只是给遗漏一条安全网。Content-Security-Policy: upgrade-insecure-requests; default-src https: unsafe-inline啥时候用HTTPS 改造中期稳定后应以资源链接全 HTTPS为目标而不是长期依赖升级指令。5 Trusted Types信任类型从 API 上消灭innerHTML泥沼定义Trusted Types配合 CSPrequire-trusted-types-for script等指令要求某些DOM XSS sink只接受「已审核」的TrustedHTML/TrustedScript对象而不是裸字符串。生产采用需要构建链与模板层支持如封装policy.createHTML。中小团队可以先把「禁止手写 innerHTML」写进 eslint 规则再评估 Trusted Types。// 语法要点概念演示实际需 policy 注册// const policy trustedTypes.createPolicy(default, { createHTML: (s) DOMPurify.sanitize(s) });// el.innerHTML policy.createHTML(userHtml);//错误示例——以为 CSP 一条 default-src 就万事大吉却对 sink 毫无约束啥时候用大型应用、强合规行业与DOMPurify 服务端消毒组合。实操建议先用 lint 规则禁止直接调用innerHTML/outerHTML/insertAdjacentHTML等高危 API把风险点在代码审查阶段暴露出来。在渲染链路中引入policy.createHTML(...)的封装层默认走服务端清洗 客户端 DOMPurify 双重消毒逐步评估启用 Trusted Types 的成本与兼容性。把典型 XSS payload 写成集成测试回归测试在 CI 中运行以防止未来改动意外打开 sink。小结把策略变成 CI/编码规则与测试用例比单纯靠文档/会议更能长期管控风险。四、CSRF跨站请求伪造与「浏览器的「好心」」1 它到底利用了啥定义已登录用户打开攻击站点攻击站点让浏览器自动向你的域发请求——Cookie 通常自动带上视SameSite。若接口仅依赖 Cookie、且无不可伪造的一次性令牌就可能被「替用户做事」。// 语法要点CSRF 攻击的是「**身份绑定在 Cookie** 且接口**不校验来源**」的组合//错误示例后端契约层面// POST /transfer 只检查 Cookie不检查 CSRF token//正确示例思路// 1) SameSiteLax/Strict见下文局限 2) CSRF token表单隐藏域 / 双 Cookie 3) 关键操作要求二次认证啥时候用所有改状态的接口转账、改邮箱、删数据。2SameSiteCookie有用但不是银弹定义你在《浏览器存储与缓存策略》读过SameSiteLax为现代浏览器默认——跨站子资源请求常常不再携带Cookie能挡不少旧式 CSRF。局限同站scheme registrable domain仍可能携带GET 仍可能被顶级导航利用Lax 对若干 GET 放行若站点需要第三方 cookie广告、嵌入世界会复杂得多。// 语法要点Set-Cookie 示意 Set-Cookie: sid...; Path/; Secure; HttpOnly; SameSiteLax啥时候用新站默认 SameSiteLax评估Strict的兼容与体验。3 CSRF Token 与「双提交」思路定义CSRF Token— 服务端下发与用户会话绑定的随机数表单或fetch头带上服务器校验。双提交 Cookie— Cookie 里一份、Header/Form 里一份服务器比对是否一致注意 XSS 会连 Cookie 与 Header 一起读——所以XSS 与 CSRF 防线要叠罗汉不是二选一。//正确示例——SPA 从登录响应拿 token存在内存或 meta每次 mutation 请求带头awaitfetch(/api/settings,{method:POST,headers:{Content-Type:application/json,X-CSRF-Token:csrfFromCookieOrMeta,},credentials:include,body:JSON.stringify(payload),});//错误示例——把 CSRF token 塞 localStorage 然后又怕 XSS —— XSS 照样读4 CSRF 与「GET 改状态」的反模式定义REST 本意里GET 应是安全、幂等的若你的删除、转账、改邮箱用 GET 或「GET 也能触发副作用」则CSRF 钓鱼链接成本极低一页img src在部分 SameSite 策略下仍可能触发顶级导航中的 GET —— 具体结合浏览器默认与 Set-Cookie 细读。精确规则凡是改状态用POST/PUT/PATCH/DELETE配合CSRF token与SameSite。!-- 错误示例用 GET 注销 —— 太容易被第三方页面引用 -- a hrefhttps://bank.example/logout不要这样设计/a啥时候用评审旧接口— 业务说「历史原因」时把这张表拍桌上。5Origin/Referer校验补 CSRF 时的注意点定义服务端可校验Origin或Referer是否来自可信域——但Referer可被用户隐私设置 /Referrer-Policy去掉Origin在多数浏览器发起的 CORS 简单/预检请求里更可靠但不是万能鉴权仍应配合 token。精确不要把「只验 Header」当成「已经防了 CSRF」——攻击页面仍可发起同源策略允许的请求形态视方法、Content-Type、Cookie 携带而定。啥时候用无 CSRF token 的老接口应急— 与后端一起定退出条件切到正式 token 方案。五、点击劫持与 UI 欺骗1X-Frame-Options与frame-ancestors定义诱导用户以为在点 A其实在点被 iframe 覆盖的 B。防御X-Frame-Options: DENY | SAMEORIGIN老而稳。CSPframe-ancestors更灵活可列多个祖先。// 正确示例 X-Frame-Options: SAMEORIGIN Content-Security-Policy: frame-ancestors self啥时候用后台管理、支付页— 默认拒绝被嵌若业务需要嵌入白名单具体父域。2X-Content-Type-Options: nosniff定义阻止浏览器嗅探非脚本类型为可执行内容降低部分上传攻击面。X-Content-Type-Options: nosniff啥时候用所有静态资源响应— 尤其用户上传文件下载接口。3 减少泄漏与权限收缩Referrer-Policy、Permissions-Policy1Referrer-Policy别把下一跳的 URL 细节送给全世界定义控制是否在请求里附带Referer以及附带多少完整路径、是否含源。收紧可减轻路径/查询串泄露太严可能影响合法的分析与安全反爬策略——要和业务一起定。// 语法要点响应头示意 Referrer-Policy: strict-origin-when-cross-origin!-- 兜底页面级——别替代全站策略 --metanamereferrercontentstrict-origin-when-cross-origin/啥时候用URL 带敏感 id—— 与日志脱敏、权限校验一起看。2Permissions-Policy哪些强大 API 在整站「默认关掉」定义以前常叫 Feature Policy告知浏览器本页及子 iframe能否使用摄像头、麦克风、地理位置、全屏、PaymentRequest、USB 等能力。被禁止时调用会失败或抛错依 API 而定。Permissions-Policy: camera(), microphone(), geolocation(self), payment(self https://pay.example.com)啥时候用嵌入未知第三方 iframe 的内容站— 先禁再按需开比出事下架便宜。3Cross-Origin-Opener-Policy/Cross-Origin-Embedder-Policy进阶跨源隔离定义这对响应头把文档放入更严格的跨源隔离环境是启用SharedArrayBuffer等能力的常见前置也会改变window.opener、弹窗集成行为。乱上会导致 OAuth 弹窗、统计脚本异常——必须与全链路联调后再开。Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp啥时候用WASM 多线程 共享内存等高性能场景普通后台 CRUD别跟风。六、敏感信息与「浏览器永远不可信」1 不要把「机密」写进前端定义API 私钥、数据库密码、Stripesk_live——永远不能出现在浏览器。前端最多只有可公开或可被限制的 key配合域名白名单、后端签名。//错误示例constAWS_SECRETxxxx;//正确示例——需要签名的上传走后端签发 STS / presigned URL啥时候用代码评审第一反应搜secret、BEGIN PRIVATE KEY。2 Token 放哪localStoragevsHttpOnly Cookie定义localStorage/sessionStorage—任何页面脚本可读遇 XSS 即送。HttpOnlyCookie—脚本读不到能抵抗单纯读存储的 XSS 偷 token但不能单靠它解决 CSRF。没有 XSS 银弹CSP 严格输出编码 依赖治理是主线。//错误示例localStorage.setItem(access_token,token);// XSS一键复制//更安全的方向概括// 短生命周期 access 后端 HttpOnly refresh 轮替或全站 Cookie CSRF 防护视架构而定啥时候用做技术选型评审— 先把威胁模型写在白板上再选 storage。3 URL 与日志别把秘密放查询串定义GET ?token会出现在浏览器历史、Referer、服务器访问日志。正确Authorization头或POST body仍要 HTTPS。//错误示例location.hrefhttps://api.example.com/me?tokentoken;//正确示例fetch(https://api.example.com/me,{headers:{Authorization:Bearer${token}}});啥时候用第三方 OAuth 回调设计—— 当心 URL fragment 与 code 交换流程。七、HTTPS、混合内容、HSTS1 为什么「全站 HTTPS」不是可以随便省略的套话定义不加密的传输可被窃听与篡改包括 Cookie、个性化内容。混合内容HTTPS 页中的 HTTP 子资源会被浏览器升级拦截或阻断。// 语法要点Secure Cookie 只在 HTTPS 发送//错误示例——生产环境仍 http 提供登录页啥时候用所有登录态页面— 与《网络层》「Mixed Content」一节呼应。2 HSTS强制 HTTPS 记忆定义Strict-Transport-Security让浏览器记住应用只走 HTTPS减少SSL 剥离攻击。Strict-Transport-Security: max-age31536000; includeSubDomains; preload啥时候用域名稳定后由后端开启小心子域还没 ready 就开includeSubDomains导致大面积故障。八、依赖供应链与第三方脚本1 npm 包不是「免安检乘客」定义锁文件package-lock/pnpm-lock、SRI子资源完整性、最小权限的第三方域名—— 减少「构建即被改包」与「CDN 被投毒」的面积。!-- 正确示例SRI --scriptsrchttps://cdn.example.com/lib.jsintegritysha384-...crossoriginanonymous/script啥时候用接入统计、客服浮窗、地图 SDK— 评估可否延迟加载、子资源完整性。可落地实践依赖与第三方在 CI 中强制使用锁文件npm ci/pnpm install --frozen-lockfile并把生成的 SBOM 附到构建记录。对接入的新第三方脚本做最小化域名隔离、延迟加载与 SRI重要页面优先使用子域隔离或 sandbox iframe。建立「新包审批」流程查看 weekly downloads、repository 链接与贡献历史对可疑包使用私有 registry 白名单。把npm audit作为周期性风险评估的一环建立工单/跟踪规则对于高危 CVE 指定 RTO 并评估修复成本。使用自动化依赖更新Dependabot/renovate但保留人工审查CI 中跑集成测试与 bundle 比对以捕捉行为差异。落地理由把偶发风险变成可追溯、可回滚的事件流便于审计与快速响应。2 lockfile、CI 与「可重现构建」定义没有锁文件 同一条npm install在不同天可能得到不同依赖树——供应链攻击里这是放大器。精确做法版本库里提交锁文件CI 用npm ci或 pnpm/yarn 等价命令做确定性安装重大升级走PR diff 依赖树而不是「随手 upgrade 大版本」。# 语法要点CI 里用 ci 而不是 install依包管理器择一npmci#错误示例——生产镜像构建脚本写 npm install --no-package-lock啥时候用被审计的客户— 第一条就查你有没有 lockfile 与 SBOM。3npm audit不是万能但也不是摆设定义audit报告的是已知漏洞数据库与依赖匹配会有误报/漏报——但它能迫使团队周期性回答「我们是否仍在用有 CVE 的传递依赖」。精确态度不把 audit 当门禁的唯一真理也不永远npm audit fix --force大版本蹦级——先看 breaking change。啥时候用发版前— 至少扫一遍高危项要有工单跟踪号。4 拼写近似包名typosquatting定义攻击者发布lodashs/react-domm之类名字蹭安装失误或postinstall 脚本挖矿。精确预防安装新包前看 weekly downloads、repository 链接、Readme 是否正常组织内用私有 registry 审批名单。5 「放一个script就把键盘交出去」定义第三方脚本与你的页面同源视角下权限极大——能读非 HttpOnly 的存储、能改写 DOM、能劫持fetch。治理 盲目接入。//正确示例——沙箱 iframe 承载极端第三方有代价未必可行仅思路// 业务上更多用「合同 SLA 子资源审计」//错误示例——为了统计把主站密钥放 window 全局啥时候用增长团队要加七个小工具— 安全评审合成一条 PR。九、postMessage跨窗口别「来者不拒」定义父子窗口 / iframe /window.open通信用postMessage。必须event.origin白名单校验敏感数据别明文广播。//正确示例window.addEventListener(message,(event){if(event.origin!https://trusted.example.com)return;// 再处理 event.data});//错误示例window.addEventListener(message,(event){eval(event.data);// 任意来源 任意代码});啥时候用OAuth popup 回调、嵌入支付页。十、实战清单上线前快速扫一遍项做什么输出默认textContentHTML 必过可信模板或消毒头CSP先 Report-Only、frame-ancestors、nosniff、HSTS、Referrer-Policy、Permissions-Policy按需会话HttpOnlySecureSameSiteCSRF 策略与后端对齐存储禁明文长期密钥最小化localStorage中的高价值依赖锁版本 审计CDN 用 SRI第三方延迟加载、域名最小化、合同 SLA日志URL 去敏前端报错上报脱敏自勉安全条款读起来像「行政通知」但每一句背后都有真实血汗账单。写漂亮 UI 是本事不让用户流血是本分。十一、结语前端在安全防御链路中的核心职责减少攻击面、严控能执行的输入、以及把风险可观测化与可回滚化。关键带走点输出永远优先文本textContent、模板引擎转义对富文本使用服务端消毒 客户端二次消毒。用 CSP先 Report-Only和 Trusted Types 对「执行语义」进行权限收缩不要把 CSP 当万能钥匙。对会话与敏感数据采用 HttpOnly Secure 合理的 SameSite 策略并把 CSRF token / 二次确认作为重要操作的必备防线。依赖管理要把随机性降到最低锁文件、CI 的确定性安装、私有 registry 与审查流程把npm audit与自动化更新变成可追溯的过程。第三方脚本需最小权限、延迟加载、SRI 或子域隔离把风险纳入合同与 SLA。行动清单上线前至少完成在 CI 中启用锁文件校验与 SBOM 生成2. 部署 CSP 报告模式并收集误报3. 建立第三方脚本接入审批与 SRI 检查4. 在代码审查中强制检查高危 sinkinnerHTML、eval 等。这些措施成本可控、见效明确把日常开发从「偶然犯错」转成「可管理的风险」。
前端安全边界
前端安全边界一、导读1 怎么读这篇笔记定义先讲「威胁从哪里来、浏览器默认帮你挡了什么」再讲你该怎么写代码才不拆墙。代码块含// 语法要点、//正确示例、//错误示例安全话题也要能落地到代码而不是口号集合。啥时候用每个小节后列「联调、上线、接入第三方脚本」常见触点。与前文的关系你在《前端网络层》里写过 Cookie 与 CORS在《浏览器存储》里写过 HttpOnly、SameSite本篇把它们放进统一威胁模型里读而不是三条孤立知识点。目标能说清XSS / CSRF / 点击劫持 / 敏感信息落点四类主线的原理边界能在评审里指出「这句 innerHTML 为什么危险、这个登录态为什么不该进 localStorage、这段 CORS 配置到底方便了谁」。免责声明安全是系统级问题前端守住的是输入输出与最小暴露面。任何「靠前端加密密钥」的方案都不成立——因为浏览器里的字符串用户都能看见。2 威胁建模速记STRIDE知道一张表就够定义STRIDE是微软经典威胁分类法用以穷举思考攻击面不是前端独舞但产品评审时很好用字母含义前端常触点SSpoofing 伪装冒充用户/站点会话窃取、钓鱼页 UITTampering 篡改改数据、改包无完整性校验的静态资源、被劫持 CDNRRepudiation 抵赖否认操作审计日志应在服务端IInformation Disclosure 泄露不该看的被看了XSS、Referer、错误栈上传DDenial of Service 拒绝服务拖垮可用性主线程炸弹、无限弹窗体验/可用性EElevation of Privilege 提权低权限变高权限越权接口、IDOR本篇不展开每一条的攻防全书但你在 PR 描述里写一句「此项改动影响 STRIDE 的 I/T」——评审档次立刻不一样。二、先搭「威胁模型」我们在防谁1 资产、威胁、暴露面定义资产— 用户数据、会话、钱、声誉。威胁— 窃取会话、伪造请求、钓鱼、植入恶意脚本、拖库多在后端。暴露面— 浏览器能读写的所有入口HTML拼接、postMessage、URL 参数、第三方脚本、存储 API。前端的目标不是「绝对不被黑」而是提高攻击成本、避免低级失误把大门敞开。// 语法要点凡是「把不可信字符串塞进能执行的环境」都是高危//错误示例element.innerHTMLuserInput;//正确示例——先问这是纯文本还是富文本纯文本就用 textContentelement.textContentuserInput;啥时候用设计阶段先列数据从哪来、到哪去——再选渲染方式。2 「同源策略」与「不是万能的」定义同源策略限制不同源的页面读对方 DOM / 默认不读响应体——所以才有CORS。但它挡不住同源下的XSS脚本已经“成为页面的一部分”。CSRF浏览器会自动带 Cookie。点击劫持视觉欺骗。供应链投毒你主动import了坏包。// 语法要点CORS 是放松读权限不是「鉴权」//错误示例——以为「开 CORS」就等于安全接口// Access-Control-Allow-Origin: * 只会让浏览器允许页面读响应攻击者自有办法从用户浏览器发请求//正确示例——身份校验仍靠 Cookie CSRF 防御 / Token 正确存储策略或后端会话体系啥时候用评审后端 CORS— 问我们到底允许哪些源读有没有credentials三、XSS跨站脚本与「可执行上下文」1 反射型 / 存储型 / DOM 型记语义定义简化教学版反射型— 恶意输入立刻从 URL 等弹回页面常用于钓鱼链接。存储型— 恶意内容进了数据库每次打开页面都会执行。DOM 型— 纯前端路由把不可信数据写进 DOM /eval不经由后端存储也能出事。共同点不信任的数据进了可执行或可被解析为 HTML的通道。// 语法要点危险的「sink」 innerHTML、outerHTML、insertAdjacentHTML、document.write、eval、new Function、setTimeout(字符串)、URL 的 javascript: 协议 href//错误示例div.innerHTMLp${name}/p;// name 含 img srcx onerror... 即炸//正确示例——模板引擎默认转义 CSP啥时候用评论、昵称、搜索词回显— 预设它就是坏的。2 输出编码与「上下文相关」定义HTML 转义不等价于JavaScript 字符串转义、也不等价于URL 编码。在HTML 文本节点里要 在属性里要注意引号闭合在javascript:URL里几乎一切都是毒。输出上下文典型坑原则性做法HTML 文本节点触发标签textContent或模板引擎 HTML-escapeHTML 属性值双引号... onload...断引号属性实体转义 能用布尔/严格枚举就别字符串裸插URLhref/srcjavascript:、data:text/htmlhttp(s)白名单URLAPI 解析禁止javascript:CSSstyle/style属性expression()、-moz-binding历史尽量不动态拼完整样式字符串用类名切换JSON进script typeapplication/json/script断出有脚本按 JSON 规则转义并由后端生成切勿字符串拼接一条总原则任何「把不可信数据」写进能产生执行语义的位置都是高危 sink先定上下文再谈转义表——不要幻想「统一escapeHtml一把梭」。//正确示例——把用户输入当作**文本节点**constpdocument.createElement(p);p.textContentuserName;root.append(p);//错误示例——模板字符串拼 hrefa.hrefjavascript:alert(1);// 若 user 可控灾难//正确示例——http(s) 白名单校验后再赋值functionsafeHref(url){try{constunewURL(url,location.href);if(u.protocolhttps:||u.protocolhttp:)returnu.href;}catch{/* ignore */}returnabout:blank;}啥时候用富文本编辑器— 需要HTML 消毒服务器侧为主客户端可第二层而不是「禁止用户输」这种鸵鸟策略。3 CSP内容安全策略从「补洞」到「限权」定义Content-Security-Policy响应头告诉浏览器哪些源可以执行脚本、加载图片、连接接口。典型default-src selfscript-src self拒绝内联脚本除非nonce/hashobject-src nonebase-uri selfframe-ancestors none或具体列表防嵌套点击劫持与 X-Frame-Options 协同// 语法要点响应头示意不是 JS Content-Security-Policy: default-src self; img-src self data:; connect-src self https://api.example.com; frame-ancestors none; base-uri self;//正确示例——静态站点先上「报告模式」观察误拦// Content-Security-Policy-Report-Only: ...//错误示例——script-src 写 unsafe-inline 还自以为「上了 CSP」// 内联脚本仍可执行XSS 仍快乐啥时候用上线前— 与后端协同下发 CSP先从 Report-Only 收集误杀。4upgrade-insecure-requests与block-all-mixed-content定义在「暂时还有个别 http 子资源」的迁移期可用 CSPupgrade-insecure-requests自动把http://资源请求升级为https://若服务端不存在会 404但避免混内容被动攻击面。block-all-mixed-content或等价策略直接拦混合内容更硬。精确注意这不替代你在资源 URL 上修正确链接只是给遗漏一条安全网。Content-Security-Policy: upgrade-insecure-requests; default-src https: unsafe-inline啥时候用HTTPS 改造中期稳定后应以资源链接全 HTTPS为目标而不是长期依赖升级指令。5 Trusted Types信任类型从 API 上消灭innerHTML泥沼定义Trusted Types配合 CSPrequire-trusted-types-for script等指令要求某些DOM XSS sink只接受「已审核」的TrustedHTML/TrustedScript对象而不是裸字符串。生产采用需要构建链与模板层支持如封装policy.createHTML。中小团队可以先把「禁止手写 innerHTML」写进 eslint 规则再评估 Trusted Types。// 语法要点概念演示实际需 policy 注册// const policy trustedTypes.createPolicy(default, { createHTML: (s) DOMPurify.sanitize(s) });// el.innerHTML policy.createHTML(userHtml);//错误示例——以为 CSP 一条 default-src 就万事大吉却对 sink 毫无约束啥时候用大型应用、强合规行业与DOMPurify 服务端消毒组合。实操建议先用 lint 规则禁止直接调用innerHTML/outerHTML/insertAdjacentHTML等高危 API把风险点在代码审查阶段暴露出来。在渲染链路中引入policy.createHTML(...)的封装层默认走服务端清洗 客户端 DOMPurify 双重消毒逐步评估启用 Trusted Types 的成本与兼容性。把典型 XSS payload 写成集成测试回归测试在 CI 中运行以防止未来改动意外打开 sink。小结把策略变成 CI/编码规则与测试用例比单纯靠文档/会议更能长期管控风险。四、CSRF跨站请求伪造与「浏览器的「好心」」1 它到底利用了啥定义已登录用户打开攻击站点攻击站点让浏览器自动向你的域发请求——Cookie 通常自动带上视SameSite。若接口仅依赖 Cookie、且无不可伪造的一次性令牌就可能被「替用户做事」。// 语法要点CSRF 攻击的是「**身份绑定在 Cookie** 且接口**不校验来源**」的组合//错误示例后端契约层面// POST /transfer 只检查 Cookie不检查 CSRF token//正确示例思路// 1) SameSiteLax/Strict见下文局限 2) CSRF token表单隐藏域 / 双 Cookie 3) 关键操作要求二次认证啥时候用所有改状态的接口转账、改邮箱、删数据。2SameSiteCookie有用但不是银弹定义你在《浏览器存储与缓存策略》读过SameSiteLax为现代浏览器默认——跨站子资源请求常常不再携带Cookie能挡不少旧式 CSRF。局限同站scheme registrable domain仍可能携带GET 仍可能被顶级导航利用Lax 对若干 GET 放行若站点需要第三方 cookie广告、嵌入世界会复杂得多。// 语法要点Set-Cookie 示意 Set-Cookie: sid...; Path/; Secure; HttpOnly; SameSiteLax啥时候用新站默认 SameSiteLax评估Strict的兼容与体验。3 CSRF Token 与「双提交」思路定义CSRF Token— 服务端下发与用户会话绑定的随机数表单或fetch头带上服务器校验。双提交 Cookie— Cookie 里一份、Header/Form 里一份服务器比对是否一致注意 XSS 会连 Cookie 与 Header 一起读——所以XSS 与 CSRF 防线要叠罗汉不是二选一。//正确示例——SPA 从登录响应拿 token存在内存或 meta每次 mutation 请求带头awaitfetch(/api/settings,{method:POST,headers:{Content-Type:application/json,X-CSRF-Token:csrfFromCookieOrMeta,},credentials:include,body:JSON.stringify(payload),});//错误示例——把 CSRF token 塞 localStorage 然后又怕 XSS —— XSS 照样读4 CSRF 与「GET 改状态」的反模式定义REST 本意里GET 应是安全、幂等的若你的删除、转账、改邮箱用 GET 或「GET 也能触发副作用」则CSRF 钓鱼链接成本极低一页img src在部分 SameSite 策略下仍可能触发顶级导航中的 GET —— 具体结合浏览器默认与 Set-Cookie 细读。精确规则凡是改状态用POST/PUT/PATCH/DELETE配合CSRF token与SameSite。!-- 错误示例用 GET 注销 —— 太容易被第三方页面引用 -- a hrefhttps://bank.example/logout不要这样设计/a啥时候用评审旧接口— 业务说「历史原因」时把这张表拍桌上。5Origin/Referer校验补 CSRF 时的注意点定义服务端可校验Origin或Referer是否来自可信域——但Referer可被用户隐私设置 /Referrer-Policy去掉Origin在多数浏览器发起的 CORS 简单/预检请求里更可靠但不是万能鉴权仍应配合 token。精确不要把「只验 Header」当成「已经防了 CSRF」——攻击页面仍可发起同源策略允许的请求形态视方法、Content-Type、Cookie 携带而定。啥时候用无 CSRF token 的老接口应急— 与后端一起定退出条件切到正式 token 方案。五、点击劫持与 UI 欺骗1X-Frame-Options与frame-ancestors定义诱导用户以为在点 A其实在点被 iframe 覆盖的 B。防御X-Frame-Options: DENY | SAMEORIGIN老而稳。CSPframe-ancestors更灵活可列多个祖先。// 正确示例 X-Frame-Options: SAMEORIGIN Content-Security-Policy: frame-ancestors self啥时候用后台管理、支付页— 默认拒绝被嵌若业务需要嵌入白名单具体父域。2X-Content-Type-Options: nosniff定义阻止浏览器嗅探非脚本类型为可执行内容降低部分上传攻击面。X-Content-Type-Options: nosniff啥时候用所有静态资源响应— 尤其用户上传文件下载接口。3 减少泄漏与权限收缩Referrer-Policy、Permissions-Policy1Referrer-Policy别把下一跳的 URL 细节送给全世界定义控制是否在请求里附带Referer以及附带多少完整路径、是否含源。收紧可减轻路径/查询串泄露太严可能影响合法的分析与安全反爬策略——要和业务一起定。// 语法要点响应头示意 Referrer-Policy: strict-origin-when-cross-origin!-- 兜底页面级——别替代全站策略 --metanamereferrercontentstrict-origin-when-cross-origin/啥时候用URL 带敏感 id—— 与日志脱敏、权限校验一起看。2Permissions-Policy哪些强大 API 在整站「默认关掉」定义以前常叫 Feature Policy告知浏览器本页及子 iframe能否使用摄像头、麦克风、地理位置、全屏、PaymentRequest、USB 等能力。被禁止时调用会失败或抛错依 API 而定。Permissions-Policy: camera(), microphone(), geolocation(self), payment(self https://pay.example.com)啥时候用嵌入未知第三方 iframe 的内容站— 先禁再按需开比出事下架便宜。3Cross-Origin-Opener-Policy/Cross-Origin-Embedder-Policy进阶跨源隔离定义这对响应头把文档放入更严格的跨源隔离环境是启用SharedArrayBuffer等能力的常见前置也会改变window.opener、弹窗集成行为。乱上会导致 OAuth 弹窗、统计脚本异常——必须与全链路联调后再开。Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp啥时候用WASM 多线程 共享内存等高性能场景普通后台 CRUD别跟风。六、敏感信息与「浏览器永远不可信」1 不要把「机密」写进前端定义API 私钥、数据库密码、Stripesk_live——永远不能出现在浏览器。前端最多只有可公开或可被限制的 key配合域名白名单、后端签名。//错误示例constAWS_SECRETxxxx;//正确示例——需要签名的上传走后端签发 STS / presigned URL啥时候用代码评审第一反应搜secret、BEGIN PRIVATE KEY。2 Token 放哪localStoragevsHttpOnly Cookie定义localStorage/sessionStorage—任何页面脚本可读遇 XSS 即送。HttpOnlyCookie—脚本读不到能抵抗单纯读存储的 XSS 偷 token但不能单靠它解决 CSRF。没有 XSS 银弹CSP 严格输出编码 依赖治理是主线。//错误示例localStorage.setItem(access_token,token);// XSS一键复制//更安全的方向概括// 短生命周期 access 后端 HttpOnly refresh 轮替或全站 Cookie CSRF 防护视架构而定啥时候用做技术选型评审— 先把威胁模型写在白板上再选 storage。3 URL 与日志别把秘密放查询串定义GET ?token会出现在浏览器历史、Referer、服务器访问日志。正确Authorization头或POST body仍要 HTTPS。//错误示例location.hrefhttps://api.example.com/me?tokentoken;//正确示例fetch(https://api.example.com/me,{headers:{Authorization:Bearer${token}}});啥时候用第三方 OAuth 回调设计—— 当心 URL fragment 与 code 交换流程。七、HTTPS、混合内容、HSTS1 为什么「全站 HTTPS」不是可以随便省略的套话定义不加密的传输可被窃听与篡改包括 Cookie、个性化内容。混合内容HTTPS 页中的 HTTP 子资源会被浏览器升级拦截或阻断。// 语法要点Secure Cookie 只在 HTTPS 发送//错误示例——生产环境仍 http 提供登录页啥时候用所有登录态页面— 与《网络层》「Mixed Content」一节呼应。2 HSTS强制 HTTPS 记忆定义Strict-Transport-Security让浏览器记住应用只走 HTTPS减少SSL 剥离攻击。Strict-Transport-Security: max-age31536000; includeSubDomains; preload啥时候用域名稳定后由后端开启小心子域还没 ready 就开includeSubDomains导致大面积故障。八、依赖供应链与第三方脚本1 npm 包不是「免安检乘客」定义锁文件package-lock/pnpm-lock、SRI子资源完整性、最小权限的第三方域名—— 减少「构建即被改包」与「CDN 被投毒」的面积。!-- 正确示例SRI --scriptsrchttps://cdn.example.com/lib.jsintegritysha384-...crossoriginanonymous/script啥时候用接入统计、客服浮窗、地图 SDK— 评估可否延迟加载、子资源完整性。可落地实践依赖与第三方在 CI 中强制使用锁文件npm ci/pnpm install --frozen-lockfile并把生成的 SBOM 附到构建记录。对接入的新第三方脚本做最小化域名隔离、延迟加载与 SRI重要页面优先使用子域隔离或 sandbox iframe。建立「新包审批」流程查看 weekly downloads、repository 链接与贡献历史对可疑包使用私有 registry 白名单。把npm audit作为周期性风险评估的一环建立工单/跟踪规则对于高危 CVE 指定 RTO 并评估修复成本。使用自动化依赖更新Dependabot/renovate但保留人工审查CI 中跑集成测试与 bundle 比对以捕捉行为差异。落地理由把偶发风险变成可追溯、可回滚的事件流便于审计与快速响应。2 lockfile、CI 与「可重现构建」定义没有锁文件 同一条npm install在不同天可能得到不同依赖树——供应链攻击里这是放大器。精确做法版本库里提交锁文件CI 用npm ci或 pnpm/yarn 等价命令做确定性安装重大升级走PR diff 依赖树而不是「随手 upgrade 大版本」。# 语法要点CI 里用 ci 而不是 install依包管理器择一npmci#错误示例——生产镜像构建脚本写 npm install --no-package-lock啥时候用被审计的客户— 第一条就查你有没有 lockfile 与 SBOM。3npm audit不是万能但也不是摆设定义audit报告的是已知漏洞数据库与依赖匹配会有误报/漏报——但它能迫使团队周期性回答「我们是否仍在用有 CVE 的传递依赖」。精确态度不把 audit 当门禁的唯一真理也不永远npm audit fix --force大版本蹦级——先看 breaking change。啥时候用发版前— 至少扫一遍高危项要有工单跟踪号。4 拼写近似包名typosquatting定义攻击者发布lodashs/react-domm之类名字蹭安装失误或postinstall 脚本挖矿。精确预防安装新包前看 weekly downloads、repository 链接、Readme 是否正常组织内用私有 registry 审批名单。5 「放一个script就把键盘交出去」定义第三方脚本与你的页面同源视角下权限极大——能读非 HttpOnly 的存储、能改写 DOM、能劫持fetch。治理 盲目接入。//正确示例——沙箱 iframe 承载极端第三方有代价未必可行仅思路// 业务上更多用「合同 SLA 子资源审计」//错误示例——为了统计把主站密钥放 window 全局啥时候用增长团队要加七个小工具— 安全评审合成一条 PR。九、postMessage跨窗口别「来者不拒」定义父子窗口 / iframe /window.open通信用postMessage。必须event.origin白名单校验敏感数据别明文广播。//正确示例window.addEventListener(message,(event){if(event.origin!https://trusted.example.com)return;// 再处理 event.data});//错误示例window.addEventListener(message,(event){eval(event.data);// 任意来源 任意代码});啥时候用OAuth popup 回调、嵌入支付页。十、实战清单上线前快速扫一遍项做什么输出默认textContentHTML 必过可信模板或消毒头CSP先 Report-Only、frame-ancestors、nosniff、HSTS、Referrer-Policy、Permissions-Policy按需会话HttpOnlySecureSameSiteCSRF 策略与后端对齐存储禁明文长期密钥最小化localStorage中的高价值依赖锁版本 审计CDN 用 SRI第三方延迟加载、域名最小化、合同 SLA日志URL 去敏前端报错上报脱敏自勉安全条款读起来像「行政通知」但每一句背后都有真实血汗账单。写漂亮 UI 是本事不让用户流血是本分。十一、结语前端在安全防御链路中的核心职责减少攻击面、严控能执行的输入、以及把风险可观测化与可回滚化。关键带走点输出永远优先文本textContent、模板引擎转义对富文本使用服务端消毒 客户端二次消毒。用 CSP先 Report-Only和 Trusted Types 对「执行语义」进行权限收缩不要把 CSP 当万能钥匙。对会话与敏感数据采用 HttpOnly Secure 合理的 SameSite 策略并把 CSRF token / 二次确认作为重要操作的必备防线。依赖管理要把随机性降到最低锁文件、CI 的确定性安装、私有 registry 与审查流程把npm audit与自动化更新变成可追溯的过程。第三方脚本需最小权限、延迟加载、SRI 或子域隔离把风险纳入合同与 SLA。行动清单上线前至少完成在 CI 中启用锁文件校验与 SBOM 生成2. 部署 CSP 报告模式并收集误报3. 建立第三方脚本接入审批与 SRI 检查4. 在代码审查中强制检查高危 sinkinnerHTML、eval 等。这些措施成本可控、见效明确把日常开发从「偶然犯错」转成「可管理的风险」。