浏览器 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
2
3
Access to script at 'https://xxxx/a.js' from origin 'https://yyyy'
has been blocked by CORS policy:
Permission was denied for this request to access the `unknown` address space.

开发者工具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
2
3
Access to script at 'https://xxxx/a.js' from origin 'https://yyyy'
has been blocked by CORS policy:
Permission was denied for this request to access the `unknown` address space.

几个关键信息:

  • 资源 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 近几年在做的两件事:

p-1.png

地址空间(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 规范

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 相关重要节点

四、回到这次案例:为什么会在 script 上报unknown address space

把前面的信息套进来,我们可以比较有把握地还原一下这次的问题链路:

  1. 顶层 页面Bhttps://zzzz(public 站点);
  2. iframe 中 页面Ahttps://xxxx
  3. 页面A 里引用脚本:<script src="https://xxxx/a.js"></script>
  4. 在实际的网络环境里,https://xxxx 很可能通过公司的 DNS / ZTNA / VPN 等解析到 内网或特殊网络(事实证明也确实解析到了192的网络),对 Chrome 来说 IP 所属地址空间是 private 或 unknown;
  5. Chrome 142 之后,LNA 生效:
    • 以 “顶层站点”的身份在访问 xxxx 对应的(unknown / private)地址空间;
    • 又没有在 iframe 上声明 allow="local-network-access"
    • 可能还有站点级 LNA 权限尚未授予或被用户/策略 Block;

于是这条 script 请求在发出前就被 LNA 拦截,DevTools 以统一形式打印:

1
2
3
Access to script at 'https://xxxx/a.js' from origin 'https://yyyy'
has been blocked by CORS policy:
Permission was denied for this request to access the `unknown` address space.

从浏览器视角看,这是:一个 public 顶层站点在未经授权的情况下,试图从 iframe 中访问 unknown/local/private 网络资源
从我们的视角看,就是:明明是同一个脚本 URL,顶层页面能加载,iframe 就报奇怪的 CORS

实质上是 LNA + iframe 权限 + 地址空间判断 的组合拳。

五、快速验证 / 自查清单

在遇到 ...unknown/private/local address space 报错时,可以按下面的顺序快速确认是否为 LNA/PNA:

  1. DevTools → Network,查看失败请求解析到的 IP,判断是否落在 127.0.0.1 / 10.x / 192.168.x / VPN 或 Zero Trust 网段(unknown/private/local)。
  2. 如果是 iframe 场景,临时给 iframe 加 allow="local-network-access" 验证是否能消除报错;多层 iframe 需层层透传。
  3. 查看地址栏 → 站点设置 → Local Network Access 权限是否被 Block/Prompt,必要时手动 Allow(仅用于诊断)。
  4. 临时切换 chrome://flags#local-network-access-check 为 Non-blocking/Disabled 验证(只用于定位,不是正式解决方案)。
  5. 若目标在私网/本机,检查响应是否包含 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"
  • 如果脚本/接口确实在私网/本机,补充 PNA 相关 header(兼容旧实现)
    • 在已有 CORS 响应头基础上增加:Access-Control-Allow-Private-Network: true
    • 并确保预检 OPTIONS 请求能正确返回这个头。
  • 调试阶段可通过 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
2
3
4
5
6
7
8
9
10
11
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/privkey.pem;

location / {
proxy_pass http://10.1.2.3:8080;
proxy_set_header Host api.example.com;
}
}

这样页面与 API 同为 public 地址空间,LNA/PNA 不再触发;同时保持 HTTPS,方便复用现有 CORS/缓存策略。

6.4 排查思路总结:以后再遇到类似报错可以这样查

1
2
... has been blocked by CORS policy:
Permission was denied for this request to access the `unknown/local/private` address space.

可以按下面这条“套路”来排查:

  1. 先确认是不是传统 CORS:
    • 顶层打开同一页面 / 同一资源是否正常?
    • 其他浏览器(Edge/Safari/旧版 Chrome)是否正常?
  2. 关注错误信息是否包含:
    • unknown address space / private network / local network access 等关键词;
  3. 在 Network 里查看资源解析到的实际 IP:
    • 是否为 127.0.0.1 / 10.x / 192.168.x / 其它私网段;
    • 是否是某些 Zero Trust / VPN 网关转出来的地址。
  4. 检查是否为 iframe 场景 & 是否有 allow=”local-network-access”:
    • 如果顶层访问 OK,iframe 挂,优先查这里。
  5. 看站点的 Local Network Access 权限:
    • 地址栏 → 站点设置里是否被 Block;
    • 企业环境下是否有限制策略。
  6. 根据情况选择:
    • 架构改造(代理/网关,同一地址空间);
    • iframe 权限 + 站点权限;
    • 服务端补充 PNA 头,保证 preflight 正确。

最后

PNA/LNA 的本质,是浏览器在给“网页能不能碰你本机/内网”这件事重新立规矩:过去网页默认可以直接访问内网 IP、localhost 等资源,容易被用来扫内网、攻击路由器、探测隐私环境,所以 Chrome 先通过 PNA 把“公网 → 内网/本机”的请求纳入 CORS,高危路径要额外 preflight 和新头;再演进到 LNA,用权限弹窗、站点设置、企业策略和 allow=”local-network-access” 这类 iframe 权限,把决策变为“用户/企业明确授权 + 细粒度控制”;发展趋势很清晰——未来从公网页面直连内网/本机会越来越难,iframe、第三方脚本会被限制得更死,推荐模式会转向“浏览器只连受控中间层或本地代理”,对我们做前端和架构的人来说,需要把“浏览器直连内网”当成过渡方案,长期用“网关/代理 + 明确权限”的架构来规避这类风险。


相关链接