10-安全、存储与身份边界:谁能代表哪个站点、读到哪份数据
俊宁 · 日期未详文章目录
从源码阅读入口、进程边界与验证条件理解安全、存储与身份边界:谁能代表哪个站点、读到哪份数据。
本文讨论通用机制和设计判断;示意不对应任何具体产品的已交付状态。源码阅读坐标沿用原稿,本次整理不构成新的运行时验收。

阅读坐标沿用原稿:Chromium
153.0.8010.37,上游 commitb75a5a95ea1a1b55bdbfd6d9f42d47be7507fb8b。本次同步未重新进行源码与目标平台运行时验收。

一句话结论
Chromium 中“能访问什么”由 site/process、sandbox、StoragePartition、网络身份和 permission 共同决定;任何单一布尔权限都不能代表完整授权,更不能把网页、Extension、Profile 与本机能力的身份压成同一个 caller。
浏览器安全不是一把“sandbox 开关”。一次网页访问摄像头、读取 cookie、加载跨站 iframe 或调用 Browser Native API 时,至少同时涉及执行进程、站点/文档身份、网络与存储归属、用户权限和 Browser 对 child process 的能力约束。它们分别阻止不同类型的越界;任何一层都不能替其他层背书。
读安全相关源码时,不要先问“这个 API 有没有权限”,而要按顺序问:请求来自哪个 document/frame?它归哪个 site 和 Profile?数据落在哪个 Storage Partition/NetworkContext?Browser 是否允许该进程或 caller 执行这项能力?请求在导航、嵌入或撤销后是否仍成立?
五层边界共同决定一次访问
Web origin / document / frame identity
-> SiteInstance / RenderFrameHost / renderer process assignment
-> OS sandbox + ChildProcessSecurityPolicy 的进程能力限制
-> BrowserContext / StoragePartition / NetworkContext 的数据归属
-> permission、content setting、用户手势与产品 policy
-> 受限 API、网络请求、cookie 或设备操作
这是一张阅读图,而非严格的同步调用栈。例如 cookie 由 Network Service 处理,权限 prompt 常在 Browser/product 层完成,site process 选择发生在导航与 RenderFrameHost 管理中。它的价值在于避免常见错误:把“同一 Renderer process”当作“可共享登录态”,把“用户曾经允许过”当作“当前 frame 永远可用”,或把“文件已被授予”当作“任意 Renderer 都可读”。
Site Isolation:进程归属围绕 site,不是每个页面各一个进程
SiteInstance、SiteInfo、SiteInstanceGroup、RenderFrameHost 和 renderer process assignment 共同描述浏览内容在 Browser 侧的站点与进程归属。导航到不同 site、嵌入跨站 frame、发生 process swap 或恢复页面时,Browser 会据此选择、复用或替换合适的 frame/process;具体策略受浏览器模式和 feature 配置影响。
| 概念 | 解决的问题 | 不能推出的结论 |
|---|---|---|
| origin | Web 安全模型中的 scheme/host/port 身份 | 两个相同 origin 的页面必然共享所有存储或进程 |
site / SiteInfo | Browser 进程分配与隔离语义的关键输入 | 一个 site 永远只有一个 Renderer 或绝不发生切换 |
SiteInstance | 将 frame 与 site/process 选择联系起来 | 每个 tab、frame 或 document 都必有独占 SiteInstance |
RenderFrameHost | Browser 对具体 frame/document 生命周期的控制点 | 它代表的 document 在导航后仍有效 |
Site Isolation 将跨站内容的隔离与权限判断纳入 Browser-owned process/frame 模型,但不承诺“iframe 一律另起进程”。实际排查时应记录目标 frame 的 site、当前/候选 RenderFrameHost、Renderer process id、是否为主 frame、是否跨站导航和 feature 配置。只看 OS 进程数量,无法证明站点隔离是否按预期生效。
OS sandbox 与 ChildProcessSecurityPolicy:两种不同的能力限制
Renderer、GPU、Utility 等 child process 的 OS sandbox 限制系统调用、文件、设备或其他本机资源的可达范围,具体策略与平台相关。它是“进程即使被攻破,仍应被操作系统约束”的防线。
Browser 内的 ChildProcessSecurityPolicy 则记录和检查 Browser 授予 child process 的特定能力,例如可读文件、isolated file system、可 request/commit 的 origin/scheme、WebUI bindings 等。content/public/browser/child_process_security_policy.h 与 content/browser/security/cpsp/child_process_security_policy_impl.* 展示了 Grant* 和 Can* 两类入口。
两者不能混为一谈:
| 防线 | 主要约束谁 | 典型问题 |
|---|---|---|
| OS sandbox | 已运行的 child process 对操作系统资源的直接操作 | 被攻陷 Renderer 能否随意读本机文件或打开设备? |
| ChildProcessSecurityPolicy | Browser 授给 child process 的 Chromium 内能力 | 此 process 是否被授权读该文件、访问该 origin、使用某类 binding? |
| API/permission policy | 当前 document、用户、Profile 与产品策略 | 当前请求是否应被展示 prompt、允许、拒绝或取消? |
安全 review 不能只看到一处 GrantReadFile() 就结束:还要追 grant 的触发条件、绑定的 child/frame、导航/关闭后是否回收,以及 OS sandbox 是否仍是最后防线。反过来,也不能以“Renderer 在 sandbox”作为在 Browser handler 中省略 caller/target 校验的理由。
Storage Partition:数据归属不是一个目录名
BrowserContext/Profile 可以拥有一个或多个 StoragePartition。它将一组网络和 Web 存储相关状态组织到可分隔的上下文中,例如 NetworkContext、cookie、HTTP cache、service worker、code cache 等;具体数据类型和生命周期需以当前实现与配置核对。
基线的 StoragePartition 接口提供 GetNetworkContext()、Browser-process CookieManager,以及按 origin/bucket/范围清理数据的入口;StoragePartitionImpl::ClearData* 说明“清理浏览数据”不是简单删除某一个目录。清理操作可能涉及多类后端、异步回调和过滤条件,测试必须等待实际 completion,而不是只确认 UI 点击成功。
Profile / BrowserContext
-> StoragePartition
-> NetworkContext
-> CookieManager、HTTP cache、URLLoaderFactory 等网络状态
-> 其他按 partition/origin/bucket 管理的 Web 数据
这条 ownership 链解释两个产品事实:normal 与 OTR/incognito 或不同 partition 不应仅因 URL 相同就默认共用登录态;同一个 Profile 下也不应把“当前页面能访问”误写成“所有 Browser Native/Extension 调用都可读取全部数据”。实际是否分区、如何持久化、何时销毁,仍由具体 BrowserContext、StoragePartitionConfig、feature 和产品 policy 决定。
Cookie:网络身份状态有自己的访问规则
cookie 是网络状态的一部分,却不是“任意 Browser 代码都能读写的字典”。在 Network Service 中,NetworkContext 持有 CookieManager,并可建立 RestrictedCookieManager;services/network/cookie_manager.* 和 network_context.* 是相应的服务入口。Browser 侧可通过 StoragePartition 获取受控 manager,但必须尊重 current settings、request context 与调用者身份。
| 问题 | 不能用的简化 | 正确的核对方向 |
|---|---|---|
| 为什么请求没带 cookie? | “URL 一样就该有登录态” | partition/context、cookie attributes、site relationship、content settings、请求 credentials 与 policy |
| 为什么页面脚本读不到 cookie? | “Browser 里有 CookieManager,所以 JS 都可读” | HttpOnly、origin、document/frame、Web API 与 Browser manager 的不同权限 |
| 为什么清理后仍有状态? | “删了 cookie 就等于所有站点数据清空” | 清理 mask/filter/completion、cache、storage、service worker、session/账号状态 |
| 为什么无痕或嵌入页面行为不同? | “同一 Profile 名称就是同一身份” | 实际 BrowserContext、StoragePartitionConfig、OTR 和嵌入上下文 |
第三方 cookie、partitioned cookie、First-Party Sets 与相关实验会进一步改变可见性和发送条件。文章或代码不能把某一版本的默认策略写成永恒规则;要在目标版本采集 request、cookie access reason 和 NetworkContext/setting 证据。
Permission:允许的是当前受限请求,不是一张永久通行证
PermissionController 处理当前 document/worker 的权限状态、请求、订阅和重置;PermissionControllerImpl 从 BrowserContext 取得对应实现,并将请求交给 delegate/product policy。公开接口中区分了当前 document、worker、origin without context、embedded requester 等场景,这正是它不能被概括成一张 origin -> allow 表的原因。
一次权限判断的最小输入应包括:
permission type
+ requesting document/frame/worker
+ embedding/top-level context
+ origin/site 与当前 navigation 状态
+ Profile、content setting、enterprise policy
+ 用户手势、prompt 与用户选择
请求到达或 prompt 返回时,document 可能已经导航、frame 已销毁,用户也可能刚刚撤销权限。因此回调必须重新关联有效 caller;“先查一次 allow,后面异步直接使用”会造成向旧页面授予结果或绕过撤销。对设备、屏幕捕获、文件、剪贴板、Browser Native API 等高风险能力,产品还应额外定义明确用户确认和审计语义,不能只复用一个宽泛的 permission boolean。
本文已核验的源码锚点
| 主题 | 源码入口 |
|---|---|
| site/process 模型 | content/public/browser/site_instance.h、content/browser/site_instance_impl.*、site_info.*、render_frame_host_manager.* |
| child process 能力策略 | content/public/browser/child_process_security_policy.h、content/browser/security/cpsp/child_process_security_policy_impl.* |
| partition 与清理 | content/public/browser/storage_partition.h、content/browser/storage_partition_impl.* |
| 网络/cookie 服务 | services/network/network_context.*、cookie_manager.*、components/content_settings/ |
| 页面权限 | content/public/browser/permission_controller.h、content/browser/permissions/permission_controller_impl.* |
运行时验证路径
- 在 normal、OTR 和至少一个非默认 StoragePartition 中访问同一站点,记录 Cookie、cache、service worker 与清理后的实际边界。
- 在跨站 iframe、navigation、BFCache restore 和 renderer crash 中测一项 permission,确认 prompt/result 不会交给失效 document。
- 用进程列表、trace 和 Browser-side
SiteInstance/RenderFrameHost观察,验证目标 site isolation 配置,而不是只依赖“多进程”印象。
威胁推演:为什么“当前 Tab 已授权”仍然不够
Tab 是产品 UI 容器,不是稳定安全主体。一个 Tab 内可以有跨站 iframe、发生 navigation、进入 BFCache、替换 RenderFrameHost,甚至在同一 Tab ID 下变成完全不同的 document。能力若只记录 Tab ID 或 URL 字符串,晚到 reply 很可能交给错误页面;若只记录 origin,又可能忽略 Profile/StoragePartition、frame 和用户手势。
最低限度的授权记录应能回答:哪个 Profile/partition、哪个 Extension 或 WebUI caller、哪个 frame/document、哪个 origin/site、哪次用户动作、哪项 capability、有效到何时、如何撤销。Browser 侧用自己持有的对象和 token 复核,Runtime 只消费经裁决后的最小参数,不自行推断网页身份。
一次访问同时携带多种身份
安全问题经常源于把不同身份压成一个 ID。一次 Browser capability 调用可能同时具有:Profile/BrowserContext、Extension 或 WebUI caller、WebContents、frame、document、origin/site、StoragePartition、network isolation identity 和 user gesture。它们回答不同问题,不能用 URL 或 Tab ID 代替全部。
| 身份 | 主要回答 | 失效时机示例 |
|---|---|---|
| Profile/BrowserContext | 属于哪个用户环境与策略域 | Profile shutdown、OTR 会话结束 |
| Extension/WebUI identity | 谁在调用产品能力 | 扩展卸载/禁用、页面销毁 |
| frame/document | 结果属于哪一次页面生命周期 | navigation commit、frame detach |
| origin/site | Web 权限与进程隔离如何判断 | 跨 origin 导航、sandboxed origin |
| StoragePartition | 使用哪组网络与站点数据上下文 | partition/config 改变、context 销毁 |
| user activation | 用户是否在有效时间内主动触发 | activation 消费或过期 |
安全校验应贴近需要它的动作。绑定连接时冻结 caller context,可以避免每次从可伪造参数重建身份;执行敏感动作前再检查当前 document、permission 和目标状态,可以防止连接建立后发生导航或撤权。
Site、Origin、Process 三者不能互换
Origin 通常由 scheme、host、port 等决定 Web 安全边界;site 用于 Site Isolation 等更粗粒度的进程归属判断;process 是承载执行的 OS 容器。同进程中的两个 context 不因此同 origin,跨进程的两个 frame 也可能属于同一更高层 site 关系。
因此“Renderer 已隔离”只是纵深防御的一层。Browser 接收 IPC 后仍要验证该 RenderProcessHost/RenderFrameHost 是否被允许访问目标资源;Renderer 发来的 origin 字符串不能单独作为信任依据。反过来,把每个敏感动作都简化为“不同进程就安全”,会遗漏同进程多 frame 与被攻陷 Renderer 的威胁模型。
存储隔离不只发生在磁盘目录
Cookie、HTTP cache、DOM storage、IndexedDB、service worker、code cache 和 permission state 有不同 owner 与 key。StoragePartition 将多类上下文组织在一起,但具体访问仍受 origin、top-level site、partition key、cookie policy 和 Profile/OTR 状态影响。
Off-the-Record 也不是给普通 Profile 换一个路径。它要求生命周期结束后不留下不该持久化的数据,并隔离网络、存储与 Extension 行为。验证隐私模式时应检查磁盘残留、共享 service、download/外部应用交接和崩溃恢复,而不是只确认 history UI 为空。
Permission 的授权对象必须足够具体
摄像头、位置、通知、剪贴板、文件与屏幕捕获的授权语义不同。有的绑定 origin,有的要求 transient user activation,有的每次由系统 picker 返回一个受限对象,还有的同时受 OS permission 控制。统一弹窗组件不代表它们共享授权模型。
一项 permission flow 至少需要回答:谁请求、代表哪个 frame/document、请求哪种资源、用户看到什么、授权保存多久、如何撤销、导航/进程退出后怎样处理、受管理 policy 是否覆盖。只保存 allow=true 会丢掉决定安全性的上下文。
用威胁推演检查 Browser capability
可以主动构造四个攻击/故障场景:
- content script 在请求后立刻导航,让旧 reply 尝试写入新 document;
- 恶意页面伪造 Extension bridge 消息,试图调用 Browser Native API;
- 同一 Profile 的另一个 Tab 猜中 request id,读取不属于它的结果;
- OTR 与普通 Profile 同时打开相同站点,观察缓存、Runtime session 或日志是否串联。
通过条件包括高权限动作没有发生、敏感数据没有进入错误身份域、审计日志仍能定位 deny 原因且不泄露 payload;接口返回预期错误只能证明其中一部分。这样安全测试才能与产品可用性同时成立。
Deny path 也必须是产品 contract
安全拒绝不应只返回通用 false。调用方需要区分 caller 无权限、document 已失效、用户拒绝、Policy 禁止、OS 拒绝和资源不可用,才能给出正确反馈且避免无意义重试。错误分类可以稳定,内部细节和敏感策略不必全部暴露给页面;Browser 日志保留脱敏审计,UI 只展示用户可行动的信息。
Blob、data、filesystem 等非普通 URL 还要追溯创建者与继承的 origin,不能用当前显示字符串重新猜身份。
工程结论
Chromium 安全模型是一条从站点身份到本机能力的连续约束,把 Renderer 放进 sandbox 只完成其中一层。新增 AI 能力越强,越需要把 caller、数据范围、生命周期和审计写进 contract,不能依赖调用链“来自自家代码”。
继续阅读
- 09-Chrome UI、WebUI 与 Extension:产品能力如何接到 Browser: 相邻章节的前置机制。
- 11-Media、WebRTC、设备与平台能力:从 Web 请求到一帧真实音视频: 相邻章节的后续机制。
上游源码入口
文中的相对源码路径可从 Chromium 官方上游快照 定位。此入口保留原稿的上游阅读坐标,不包含任何产品定制代码,也不替代运行时验收。