【笔记】浏览器PNA/LNA策略——记一次iframe中CORS报错的问题排查经历
Dec 6, 2025浏览器
浏览器 PNA/LNA 策略——记一次 iframe 中 CORS 报错的问题排查经历
问题背景
最近在排查一个非常诡异的问题:
页面 A:正常业务页面,直接打开一切 OK。页面 B:通过<iframe src="页面 A">把 A 嵌进来。
页面A 里以<script>的方式引用了一个内部 CDN 脚本 https://xxxx/a.js,CDN 域名(代号xxxx)和页面A域名(代号yyyy)不同,页面A和页面B(代号zzzz)域名也不同。
在 Chrome(新版本)访问页面B时,控制台报错,页面无法正常访问:
1 | Access to script at 'https://xxxx/a.js' from origin 'https://yyyy' |
开发者工具Network面板中也显示这几个 js 文件请求都是CORS error
看到这个报错时整个人都懵了,因为<script>标签引用的脚本,怎么可能有 CORS 问题?像 JSONP 这种技巧不也是利用了<script>标签的特性嘛。
面对这种奇怪的现象,我做了一些实验。发现从现象上有以下奇怪的点:
- 单独访问
页面A时一切正常,cdn 的 js 不会报 CORS 错误,只有在访问页面B时(以iframe形式访问页面A)才会报错; - 报错时
<script>标签引用的脚本请求会报 CORS 错误,页面A其他 ajax 跨域请求也会报错(这些请求都做了 CORS 设置); - 尝试了其他浏览器,如 Edge/Safari,这些浏览器不会有这个问题、即
页面A可以正常访问; - 尝试了低版本的 Chrome,也不会有这个问题、即
页面A可以正常访问; - 错误里出现了
unknown address space这种字样,相比其他 CORS 错误信息是很陌生的;
结合这些信息,我基本断定是近期新版 Chrome 在网络策略上做了什么调整。在分析更新日志等信息后,最终排查下来,根因其实不是传统意义上的 CORS,而是 Chrome 最近上线的一套 Local Network Access / Private Network Access(LNA/PNA)安全策略 + iframe 权限机制 在背后搞事情。
一、现象复盘:为什么看起来像 CORS,又不像 CORS?
回顾错误信息:
1 | Access to script at 'https://xxxx/a.js' from origin 'https://yyyy' |
几个关键信息:
- 资源 URL:
https://xxxx/a.js - Origin:
https://yyyy—— 注意这是页面A的域,而不是页面B的域。 - 报错被归类为
blocked by CORS policy,但额外带了unknown address space的提示。
结合几个“奇怪”的点,这已经在提示我们:真正做决策的是“顶层站点 + 地址空间”,而不是 iframe 自己的 origin——这正是 Local Network Access / Private Network Access 的行为特征。
二、排查过程:从“CORS”到“Local Network Access”
第一步:先排除“普通 CORS 配置错误“
经典的 CORS 问题一般长这样:
1 | No 'Access-Control-Allow-Origin' header is present on the requested resource. |
或者
1 | The 'Access-Control-Allow-Origin' header has a value 'xxx' that is not equal to the supplied origin. |
而这次的错误信息多了这样一句:
1 | Permission was denied for this request to access the `unknown` address space. |
这句话用在很多类似案例里(如StackOverflow 问题示例),几乎都是 Chrome 的 Private Network Access / Local Network Access 安全检查触发 的结果,而不是简单的 CORS 头缺失。
再结合现象:
- 顶层打开
页面A正常; - iframe 场景才报错;
说明:
- 服务器对
https://xxxx这个 origin 的 CORS 配置本身是 OK 的; - 但当顶层是
页面B的域名(https://zzzz)时,Chrome 用的是另一套安全逻辑。
第二步:顺着错误关键词搜一圈
用报错中的关键字去搜:
- “Permission was denied for this request to access the ‘unknown’ address space”
- “Local Network Access Chrome 142 iframe”
- “Access to script at has been blocked by CORS policy unknown address space”
会发现大量类似提问或者官方说明,都指向这几个关键点:
- 这是 Chrome 新的 Local Network Access / Private Network Access 策略 带来的变化;
- 只在 Chrome / Chromium 系列中新版本 执行,其他浏览器暂未完全跟进,因此出现“Chrome 挂了,Edge/Safari 正常”的现象;
- 很多案例里也是 iframe 场景,并且常被误认为是“CORS 头没配好”。
第三步:对比顶层 vs iframe 的行为差异
进一步验证:
- 顶层打开
页面A(Origin 为https://xxxx)时,CDN js 资源加载正常; - 同一个 URL,iframe 场景下却被以 CORS policy + unknown address space 拦截。
这基本就把问题锁定在:
Chrome 把“顶层站点 + 目标资源”看作一次「跨地址空间访问」,并在 iframe 场景下启用了更严格的 LNA / PNA 检查。
三、策略机制科普:PNA(Private Network Access) / LNA(Local Network Access) 到底是啥?
要理解这个问题,需要先大致了解 Chrome 近几年在做的两件事:
- Private Network Access(PNA):限制公网网站随意访问内网 / 本机服务(https://developer.chrome.com/blog/private-network-access-preflight);
- Local Network Access(LNA):在 PNA 的基础上,引入“用户权限弹窗 + Permissions Policy”的新模式(https://developer.chrome.com/blog/local-network-access)。

地址空间(Address Space)划分
规范里把目标地址按“私密程度”分了几档:
public:公网 IP / 域名;private/local:内网 IP(10.x / 192.168.x / 172.16–31.x 等);loopback:本机地址(127.0.0.1 / localhost 等);unknown:浏览器无法可靠判断所属空间的时候(某些 VPN / Zero Trust / 特殊代理场景经常掉进这里)。
核心安全模型是:
从「更开放的地址空间」访问「更私密的地址空间」时,要么被禁止,要么需要额外的 CORS/PNA 头或用户授权。
Private Network Access:基于 CORS 的第一阶段
PNA(以前叫 CORS-RFC1918)是第一阶段方案:
- 目标:防止公网网站扫描 / 操作你的内网设备(路由器、打印机、本机服务等),减少 CSRF / fingerprinting 风险;
- 机制: - 对从公网(public)发往私网(private/loopback)的请求,强制加一层 预检请求(preflight):
请求头带:Access-Control-Request-Private-Network: true
服务端必须响应:Access-Control-Allow-Private-Network: true
否则直接在浏览器侧报错(通常表现为 CORS error)。
后来由于落地难度和兼容性问题,Chrome 在 2024 年宣布 PNA 全量 rollout 暂停,会基于权限模型重新设计方案。
Local Network Access:基于权限的第二阶段
2025 年开始,Chrome 推出了新的 LNA 规范:
- 核心变化:
- 从“纯 CORS + 预检”转为“用户权限 + 站点设置”;
- 当站点要访问本地 / 内网 / unknown 地址空间时,会弹出
Local Network Access权限框,让用户决定 Allow/Block(https://support.esri.com/en-us/knowledge-base/what-to-know-about-new-permission-prompt-in-google-chro-000039171); - 站点的 LNA 权限可以被持久化、也可以由企业策略统一下发。
- 时间线大致是:
- 2025-06:Chrome 138 引入 LNA 权限提示的实验 flag(chrome://flags#local-network-access-check),开发者可以主动开启测试(https://developer.chrome.com/blog/local-network-access);
- 2025-09~10:从 Chrome 141/142 开始,默认为用户开启 LNA 权限提示,对连接本地网络的场景开始实打实拦截(https://developer.chrome.com/release-notes/142)。
iframe 与 Permissions Policy:allow="local-network-access"
LNA 还跟 Permissions Policy(原 Feature Policy) 集成在一起,特别是 iframe 场景:
- 顶层页面要想让 iframe 里的代码访问本地 / 内网,需要显式声明:
1 | <iframe src="https://xxxx/pageA" allow="local-network-access"></iframe> |
- 并且如果有多层嵌套 iframe,需要层层透传这个
allow,否则最内层拿不到权限; - 旦 iframe 没被授权,iframe 里的
fetch/<script>/WebSocket等对本地/内网/unknown 地址空间的访问,就会被 Chrome 直接拦截,DevTools 里通常表现为你看到的这种 CORS+unknown address space 报错。
*PNA / LNA 相关重要节点
- 2020-11:Chrome 团队提出 CORS-RFC1918(后改名为 Private Network Access),征求反馈。https://developer.chrome.com/blog/cors-rfc1918-feedback
- 2021 Q2–Q4(Chrome 90+):
- 对从非安全上下文访问私网资源开始给出警告,并逐步拦截;
- 推出 Deprecation Trial 供站点缓冲迁移。https://developer.chrome.com/blog/private-network-access-update
- 2022-01:发布《Private Network Access: introducing preflights》,正式将 PNA 与 CORS 预检机制结合,引入 Access-Control-Allow-Private-Network 等头。https://developer.chrome.com/blog/private-network-access-preflight
- 2024-03~04:Chrome 发布 PNA 更新文章,宣布 PNA rollout 暂时搁置,将探索基于权限的替代机制,同时继续在部分场景中扩展 PNA 保护(按官方公告)。https://developer.chrome.com/blog/private-network-access-update-2024-03
- 2025-06(Chrome 138,按官方博客的计划时间线):发布《New permission prompt for Local Network Access》,引入 LNA 权限弹窗(默认通过 flag opt-in),开始用“权限 + 站点设置”模式管理本地网络访问。https://developer.chrome.com/blog/local-network-access
- 2025-09~10(Chrome 141/142,依据 release notes 标注的逐步放量):
- LNA 权限 prompt 面向更多用户逐步默认开启;
- 各大厂(Esri、Box、Stripe、SAP 等)的文档开始集中出现「Chrome 142 之后访问本地/内网服务被 CORS+unknown address space 拦截」的说明和 workaround。https://support.esri.com/en-us/knowledge-base/what-to-know-about-new-permission-prompt-in-google-chro-000039171
四、回到这次案例:为什么会在 script 上报unknown address space
把前面的信息套进来,我们可以比较有把握地还原一下这次的问题链路:
- 顶层
页面B:https://zzzz(public 站点); - iframe 中
页面A:https://xxxx; 页面A里引用脚本:<script src="https://xxxx/a.js"></script>;- 在实际的网络环境里,
https://xxxx很可能通过公司的 DNS / ZTNA / VPN 等解析到 内网或特殊网络(事实证明也确实解析到了192的网络),对 Chrome 来说 IP 所属地址空间是 private 或 unknown; - Chrome 142 之后,LNA 生效:
- 以 “顶层站点”的身份在访问
xxxx对应的(unknown / private)地址空间; - 又没有在 iframe 上声明
allow="local-network-access"; - 可能还有站点级 LNA 权限尚未授予或被用户/策略 Block;
- 以 “顶层站点”的身份在访问
于是这条 script 请求在发出前就被 LNA 拦截,DevTools 以统一形式打印:
1 | Access to script at 'https://xxxx/a.js' from origin 'https://yyyy' |
从浏览器视角看,这是:一个 public 顶层站点在未经授权的情况下,试图从 iframe 中访问 unknown/local/private 网络资源。
从我们的视角看,就是:明明是同一个脚本 URL,顶层页面能加载,iframe 就报奇怪的 CORS。
实质上是 LNA + iframe 权限 + 地址空间判断 的组合拳。
五、快速验证 / 自查清单
在遇到 ...unknown/private/local address space 报错时,可以按下面的顺序快速确认是否为 LNA/PNA:
- DevTools → Network,查看失败请求解析到的 IP,判断是否落在 127.0.0.1 / 10.x / 192.168.x / VPN 或 Zero Trust 网段(unknown/private/local)。
- 如果是 iframe 场景,临时给 iframe 加
allow="local-network-access"验证是否能消除报错;多层 iframe 需层层透传。 - 查看地址栏 → 站点设置 →
Local Network Access权限是否被 Block/Prompt,必要时手动 Allow(仅用于诊断)。 - 临时切换
chrome://flags#local-network-access-check为 Non-blocking/Disabled 验证(只用于定位,不是正式解决方案)。 - 若目标在私网/本机,检查响应是否包含
Access-Control-Allow-Private-Network: true,并确保预检OPTIONS正确返回。
六、解决方案与实践建议
6.1 架构层优选方案:尽量消灭“公网 → 内网”的跨地址空间访问
从安全模型来看,Chrome 的判断并不是“反常”,而是架构上的风险被暴露出来了:
- 业务页面部署在 public 域名上(
https://yyyy/https://xxxx); - 实际资源或 API 落在内网 / 本地 / unknown 地址空间;
- 以前浏览器默认放行,现在开始收紧访问能力。
最根本、最长期稳妥的方案是:
- 将内网服务“挂”在一层 API 网关 / 反向代理 后面,对浏览器暴露为同一地址空间:
- 例如 Nginx / API Gateway 把内网 10.x.x.x:port 代理为 https://api.example.com;
- 浏览器看到的是 public→public,不会触发 LNA/PNA。
- 或者业务页面本身就跑在内网环境中,在浏览器眼里整体都是 local/private 地址空间。
这类改造成本会高一些,但可以一次性消掉大部分 LNA/PNA 类问题。
6.2 短期或受限环境下的 workaround
在没法马上改架构的情况下,可以考虑这些策略(主要适用于内网/企业环境):
- 为站点显式授予 Local Network Access
- 手动:在 Chrome 地址栏点击锁头 → 站点设置 → 将 Local Network Access 设置为 Allow;
- 企业统一:通过 Chrome Enterprise / Edge 管理策略(如 LocalNetworkAccessAllowedForUrls 等),为指定域名统一开启本地网络访问权。
- 为 iframe 增加
allow="local-network-access"- 例如
<iframe src="https://xxxx/pageA" allow="local-network-access"></iframe> - 如果 B 里还有多层 iframe,要保证每一层都透传该权限,https://learn.microsoft.com/en-us/answers/questions/5646980/local-network-access-iframe-restrictions-causing-d
- 例如
- 如果脚本/接口确实在私网/本机,补充 PNA 相关 header(兼容旧实现)
- 在已有 CORS 响应头基础上增加:
Access-Control-Allow-Private-Network: true - 并确保预检
OPTIONS请求能正确返回这个头。
- 在已有 CORS 响应头基础上增加:
- 调试阶段可通过 flags 验证是否确实是 LNA/PNA 导致
- 比如在本机把
chrome://flags#local-network-access-check改为Non-blocking/Disabled,看问题是否消失; - 只能用于定位问题,不能作为线上解决方案。
- 比如在本机把
6.3 代理落地的简单示例
以 Nginx 反代为例,把内网 10.1.2.3:8080 挂到外层域名 api.example.com,浏览器视角是 public→public:
1 | server { |
这样页面与 API 同为 public 地址空间,LNA/PNA 不再触发;同时保持 HTTPS,方便复用现有 CORS/缓存策略。
6.4 排查思路总结:以后再遇到类似报错可以这样查
1 | ... has been blocked by CORS policy: |
可以按下面这条“套路”来排查:
- 先确认是不是传统 CORS:
- 顶层打开同一页面 / 同一资源是否正常?
- 其他浏览器(Edge/Safari/旧版 Chrome)是否正常?
- 关注错误信息是否包含:
unknown address space/private network/local network access等关键词;
- 在 Network 里查看资源解析到的实际 IP:
- 是否为 127.0.0.1 / 10.x / 192.168.x / 其它私网段;
- 是否是某些 Zero Trust / VPN 网关转出来的地址。
- 检查是否为 iframe 场景 & 是否有 allow=”local-network-access”:
- 如果顶层访问 OK,iframe 挂,优先查这里。
- 看站点的 Local Network Access 权限:
- 地址栏 → 站点设置里是否被 Block;
- 企业环境下是否有限制策略。
- 根据情况选择:
- 架构改造(代理/网关,同一地址空间);
- iframe 权限 + 站点权限;
- 服务端补充 PNA 头,保证 preflight 正确。
最后
PNA/LNA 的本质,是浏览器在给“网页能不能碰你本机/内网”这件事重新立规矩:过去网页默认可以直接访问内网 IP、localhost 等资源,容易被用来扫内网、攻击路由器、探测隐私环境,所以 Chrome 先通过 PNA 把“公网 → 内网/本机”的请求纳入 CORS,高危路径要额外 preflight 和新头;再演进到 LNA,用权限弹窗、站点设置、企业策略和 allow=”local-network-access” 这类 iframe 权限,把决策变为“用户/企业明确授权 + 细粒度控制”;发展趋势很清晰——未来从公网页面直连内网/本机会越来越难,iframe、第三方脚本会被限制得更死,推荐模式会转向“浏览器只连受控中间层或本地代理”,对我们做前端和架构的人来说,需要把“浏览器直连内网”当成过渡方案,长期用“网关/代理 + 明确权限”的架构来规避这类风险。
相关链接
- [官方] Private Network Access: introducing preflights — https://developer.chrome.com/blog/private-network-access-preflight
- [官方] Local Network Access 权限模式 — https://developer.chrome.com/blog/local-network-access
- [官方] Release notes 142(LNA 放量)— https://developer.chrome.com/release-notes/142
- [官方] PNA 更新:暂缓 rollout,探索权限模型 — https://developer.chrome.com/blog/private-network-access-update-2024-03
- [官方] CORS-RFC1918 反馈征集 — https://developer.chrome.com/blog/cors-rfc1918-feedback
- [官方] PNA 更新(Deprecation Trial 等)— https://developer.chrome.com/blog/private-network-access-update
- [官方] 在 Chrome中调整网站的 LNA 限制 - https://docs.google.com/document/d/1QQkqehw8umtAgz5z0um7THx-aoU251p705FbIQjDuGs/edit?hl=zh-cn&tab=t.0#heading=h.v8oobsqxbxxy
- [案例] Chrome LNA 权限弹窗说明(Esri)— https://support.esri.com/en-us/knowledge-base/what-to-know-about-new-permission-prompt-in-google-chro-000039171
- [案例] iframe 限制讨论(MS Q&A)— https://learn.microsoft.com/en-us/answers/questions/5646980/local-network-access-iframe-restrictions-causing-d
- [社区] StackOverflow 报错示例 — https://stackoverflow.com/questions/79823001/cors-error-permission-was-denied-for-this-request-to-access-the-unknown-addres
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com