edit | blame | history | raw

Bilibili 已登录会话完整视频扩展入口安全设计 V001

1. 事项与审核门禁

  • 项目:project-info
  • 上游需求:REQ-BILI-DYNAMIC-COLLECTOR-20260804-001
  • 开发事项:DEV-PROJECT-INFO-BILI-AUTHENTICATED-SESSION-DOWNLOAD-20260805-001
  • 管理授权:HANDOFF-INFOADMIN-INFODEV-BILI-PLUGIN-FULL-VIDEO-NARROW-SCOPE-ASSIGN-20260805-001
  • 唯一 owner:dev.developer.project / infodev
  • 独立审核员:dev.reviewer.project / inforev
  • 精确目标:青枫浦上Q / BV1HA3o6oEJJ / https://www.bilibili.com/video/BV1HA3o6oEJJ
  • 远端完整时长基线:3133.95 s;已隔离错误预览:600.133313 s
  • 分类:已有需求支撑的高安全失败成本实现。扩展、Native Messaging、认证会话和本地发布形成跨进程安全边界,因此先做一次合并安全设计审核,PASS 前禁止实现。

本 V001 仅设计“完整文件入口”增量。已经独立审核 PASS/0 的
dev/project-dev/bili_video_download_bridge.py accept-browser-file 后半程保持原字节、原 CLI 和原测试合同,不在本设计中重写或放宽。当前已安装 1.0.5 TXT 扩展、其 localhost 服务、SQLite、固定 Cookie 临时文件和 token 查询接口也保持不变且不被完整视频模式调用。

设计审核阶段外部动作固定为:真实 Cookie 读取 0、真实下载 0、Chrome/扩展安装或修改 0、Native Host 注册 0F:\video 写入 0、正式 ana-data 写入 0、600 秒隔离预览读取 0

2. 最小目标与明确排除

2.1 最小目标

在项目自有、用户可见的相邻 side-panel 模式中复用“添加任务 → 进行中/失败重试 → 已完成”的交互,完成以下单条闭环:

  1. 用户在已登录 Chrome 打开精确目标页并主动点击“校验当前页面”。
  2. 扩展仅在用户再次点击“添加完整视频任务”时读取该目标站点当前请求所需 Cookie,并通过一条专用 Native Messaging 连接传入可信本地主机内存。
  3. 本地主机使用内存 CookieJar 和 yt-dlp Python API 获取 bestvideo+bestaudio/best,由 FFmpeg 仅做流复制合并,生成一个完整 MKV。
  4. 本地主机立即调用冻结的 accept-browser-file 后半程,执行稳定性、partial、音视频流、远端/本地时长、SHA-256、CreateNew 媒体和映射门禁。
  5. 只有后半程 exit 0 时 side panel 显示“已完成”和非秘密的正式文件名;其他情况显示固定错误码并允许用户显式重试。

2.2 明确排除

  • 不做通用账号、博主、BVID 或批量下载框架;协议、界面和主机均硬绑定一个 BVID。
  • 不读取 Chrome profile 文件,不导出 Cookie,不提供 Cookie/token 查看、复制、日志或调试入口。
  • 不输出、记录或持久化签名媒体 URL,不使用人工签名 .m4s 直链。
  • 不绕过会员、充电、DRM、验证码、登录或访问控制;无法证明当前会话可完整播放时 fail-closed。
  • 不修改现有 TXT 扩展 1.0.5,不复用其 loopback/token/SQLite/Cookie 文件链路。
  • 不修改冻结后半程,不新增转写、调度、数据库、HTTP API、远程服务、GUI 应用或多任务队列。
  • 不续传、不跨启动恢复下载;失败或中断后清理内部暂存,重试必须重新校验页面并重新取得当前会话 Cookie。

3. 威胁模型与不可变安全合同

3.1 受保护数据

受保护数据包括 Cookie 名称和值、会话 token、密码、验证码、内部解析得到的签名清单/媒体 URL,以及包含这些值的异常、请求、调试对象和下载器 info_dict。真实值只允许短暂存在于:

  • 用户点击后运行的项目自有 Chrome 扩展内存;
  • 该扩展专属 Native Messaging 管道;
  • 可信本地主机进程、yt-dlp、内存 CookieJar 和下载所需网络库内存。

这些值不得进入 Codex、任务文本、argv、环境变量、配置、项目文件、浏览器 storage、Native Host manifest、日志、stdout/stderr、映射、栈追踪、测试、证据或 handoff。

3.2 信任边界

  • 网页内容不受信任。网页只能被扩展注入的只读校验函数观察,不能直接连接 Native Host,也不能传 Cookie、路径、命令或 BVID。
  • side panel 是唯一用户入口;background service worker 是唯一 Native Messaging 调用方。background 校验消息发送者属于本扩展 side-panel 页面。
  • Native Host manifest 的 allowed_origins 必须是安装后实际扩展 ID 对应的唯一精确 chrome-extension://<id>/,禁止占位符遗留、通配符或第二来源。
  • Native Host 不监听 TCP,不提供 localhost、CORS、HTTP、WebSocket 或远程绑定。因此本阶段不使用 loopback 的一次性 capability 例外。
  • 输出根、bridge 路径、batch JSON、目标目录、yt-dlp/FFmpeg/FFprobe 位置都来自安装时生成的非秘密、当前用户只读配置;扩展消息不能提供或覆盖任何路径、程序名、参数或命令。

3.3 fail-closed 总原则

输入 schema、来源、页面证明、Cookie 边界、认证元数据、DRM、格式、下载、合并、候选唯一性、后半程或清理任一环节不能满足合同,任务均不得显示 COMPLETE、不得创建正式媒体/映射、不得把 600 秒预览交给转写。未知异常只映射为固定安全错误码,原异常文本与对象不出进程。

4. 组件和数据流

用户可见 side panel
  └─ 用户点击校验/添加
       └─ background service worker
            ├─ 注入只读页面证明(无秘密持久化)
            ├─ chrome.cookies.getAll(精确 Bilibili URL)
            └─ chrome.runtime.connectNative(精确 host)
                  └─ 专用 Native Host
                       ├─ 严格协议校验
                       ├─ Cookie 数组 → io.StringIO CookieJar
                       ├─ yt-dlp API → 内部暂存 → FFmpeg stream-copy MKV
                       ├─ 清空并关闭内存 CookieJar
                       └─ 冻结 accept-browser-file 子进程
                            └─ F:\video\青枫浦上Q\BV1HA3o6oEJJ.mkv
                               + BV1HA3o6oEJJ.download.json

实现使用相邻的项目自有 side-panel 构建和专用 Native Host,不向当前 TXT 扩展二进制注入代码。界面状态和按钮语义复用现有可见工作流;安全边界与 localhost TXT 后端完全分离。background 持有一个长连接,Native Host 同一时刻仅允许一个任务;side panel 关闭再打开时只从 Native Host 查询非秘密的阶段、百分比、错误码和最终文件名,不使用 chrome.storage 保存任务或秘密。

5. 扩展入口合同

5.1 最小权限

扩展 manifest 只申请:

  • sidePanel:用户可见控制面;
  • activeTabscripting:仅在用户点击时读取当前页的非秘密播放证明;
  • cookieshttps://www.bilibili.com/* host permission:仅在用户点击添加/重试时读取该 URL 可发送的 Cookie;
  • nativeMessaging:连接唯一专用 Native Host。

不申请 downloadsstoragedebuggerwebRequestdeclarativeNetRequesttabs 通配访问或 127.0.0.1 权限;不注册 content script、外部消息入口或快捷键自动触发。

5.2 页面证明

用户点击“校验当前页面”后,background 通过 chrome.scripting.executeScript 取得并立刻验证:

  • hostname=www.bilibili.com 且 path 精确为 /video/BV1HA3o6oEJJ;query/hash 不作为下游输入,host 只接收冻结 canonical URL;
  • 页面主 video 元素唯一可用,readyState >= 1videoWidth > 0videoHeight > 0
  • duration3133.95 s 差值不超过 max(3.0 s, 3133.95*0.1%)
  • mediaKeys === null;出现 EME/DRM、登录/验证码/付费阻断或无法获得完整时长均 fail-closed;
  • 界面明确提示用户只在页面显示“充电中”且播放器为 52:14 时继续。该人工可见确认不是唯一门禁,后续认证元数据与最终时长仍必须独立通过。

页面校验结果只包含 BVID、规范 URL、时长、尺寸、readyState、DRM 布尔值和时间戳,不包含 DOM 文本、HTML、媒体 URL 或播放器内部对象;校验结果过期 60 秒即失效。

5.3 Cookie 最小化

只有在有效页面证明尚未过期且用户点击“添加完整视频任务”或“重试”时,background 才调用 chrome.cookies.getAll({url: "https://www.bilibili.com/"})。扩展不序列化到 storage,不显示值,不计算证据哈希,不在错误对象中附带请求。发送完成后立即清空数组引用。

Native Host 只接受 domain 为 bilibili.com 或以 .bilibili.com 结尾、path 以 / 开头的 Cookie;单个字段、Cookie 数、总 UTF-8 字节均有上限。外域、控制字符、重复关键字段、未知字段、空值、超限消息一律拒绝,且固定返回 E_SECRET_INPUT,不回显原值。

6. Native Messaging 协议

6.1 固定消息

扩展到主机只允许:

  • {"type":"status","schema":1}:无秘密状态查询;
  • {"type":"start","schema":1,"target":"BV1HA3o6oEJJ","page_proof":{...},"cookies":[...]}:一次任务;
  • {"type":"cancel","schema":1,"target":"BV1HA3o6oEJJ"}:用户显式取消。

主机到扩展只返回固定字段:schema/type/target/phase/progress/error_code/formal_filename/mapping_filename。禁止返回 URL、命令、Cookie、下载器对象、stderr、异常文本或内部绝对暂存路径。正式文件仅返回 basename;项目用法另行记录固定授权目标目录。

消息采用 Chrome Native Messaging 长度前缀;主机在分配大对象前拒绝超长帧。JSON 必须是 UTF-8、对象、无重复键、精确字段集合和正确类型。target 不允许由请求选择其他 BVID。主机全局单任务;第二个 start 返回 E_BUSY,不产生网络或文件动作。

6.2 无日志实现

主机 stdout 仅写协议帧;stderr 默认空。yt-dlp 使用自定义 logger 和 progress hook:logger 将所有第三方字符串丢弃,仅在内存更新固定错误码;progress hook只抽取 status、已下载字节、总字节和数值百分比,不保存或转发 filename/info_dict/url/fragment/headers。顶层捕获 BaseException,执行清理后发送静态码,不打印 traceback。测试模式也只使用合成哨兵,真实秘密永不进入证据。

7. 可信主机下载合同

7.1 安装与固定配置

实现阶段拟新增一个项目目录和一个测试目录;不会改动冻结 bridge:

  • dev/project-dev/bili_authenticated_extension/:manifest、side panel、background、Native Host、构建/当前用户安装脚本和非秘密配置范本;
  • dev/project-dev/test/bili_authenticated_extension/:协议、秘密不落盘、失败清理和 UI 桩测试;
  • dev-doc/project-doc/B站已登录会话完整视频扩展入口.md:安装、按钮顺序、卸载和真实验收说明。

Native Host 用 Python 实现并构建为 Windows 当前用户可执行程序;构建产物不包含真实配置或秘密。安装脚本只在后续实现审核 PASS 且由获授权操作者显式执行时:

  1. 校验实际扩展 ID 为 32 位 Chrome ID;
  2. 写入只含该 ID 的 Native Host manifest;
  3. 在 HKCU 注册专用 host;
  4. 生成当前用户可读的非秘密配置,固定 exact BVID、canonical URL、预期时长、bridge Python/脚本、batch JSON、FFmpeg/FFprobe 和已授权目标目录;
  5. 拒绝现有输出碰撞、通配目录、相对路径、网络路径和重解析点。

开发/复审不执行安装脚本,不写注册表,不启动 Chrome。打包采用固定依赖版本的 one-directory 可执行构建;不把 Cookie、会话、路径样本或运行日志打入产物。

7.2 内存 CookieJar

主机把通过 schema 的 Chrome Cookie 转换为 Netscape 格式文本并写入 io.StringIO,将这个 text stream 直接传给 yt-dlp cookiefile 参数。已核对本机 yt-dlp 2026.07.04 的 YoutubeDLCookieJar 支持文件名或 text stream;设计不使用 Cookie 临时文件、固定路径、浏览器 profile 或 --cookies-from-browser

所有 yt-dlp 操作在同一 with YoutubeDL(opts) 生命周期完成;结束或失败时依次 seek(0)truncate(0)close() 并释放 Cookie/请求引用。Python 不承诺物理内存安全擦除,但不跨进程生命周期、不持久化,符合“仅可信扩展/服务内存短暂存在”的授权边界。

7.3 认证元数据和格式门禁

下载前先在同一个认证上下文中 extract_info(download=False),只在内存检查:

  • extractor 返回单条视频,id == BV1HA3o6oEJJ,无 entries、playlist、live 或 multi-P 展开;
  • duration 满足同一 max(3.0 s, remote*0.1%) 门禁;
  • 没有 has_drm,选中格式及其组成格式均没有 DRM 标志;
  • 存在可选择的视频流和音频流;不得把单音频、封面、TXT 或 600 秒预览视为完整文件;
  • 无登录、验证码、权限或格式失败。任何无法证明项均在下载前失败。

info_dict、format URL、HTTP headers 和下载器异常只存在内存,不写 JSON/description/thumbnail/subtitle/日志/cache。禁止 verbose、dump、print、write-info、write-comments、write-playlist-metafiles、浏览器 Cookie 导出和插件加载。

7.4 下载与合并

yt-dlp 固定选项:

  • format="bestvideo+bestaudio/best"
  • merge_output_format="mkv"
  • noplaylist=Truecontinuedl=Falseoverwrites=Falsecachedir=False
  • 使用内置 HTTP 下载器,不配置外部下载器,保证签名 URL 不进入子进程 argv;
  • FFmpeg 只接收本地主机暂存的音视频文件并做 stream-copy mux,不转码、不降画质;
  • 不写 metadata、thumbnail、subtitle、description、info JSON 或 cookies。

主机从固定 %LOCALAPPDATA%\project-info\bili-auth-ingress\BV1HA3o6oEJJ\ 下独占创建随机运行目录,固定输出模板,不接受消息路径。创建、遍历和清理前确认所有路径位于固定根且不是重解析点。下载中间件只能留在该目录;任务结束只允许存在一个受支持的非 partial 媒体候选,出现多个候选、未知 sidecar、.crdownload/.part/.tmp 残留或文本型 URL 状态文件即 fail-closed 并清理。启动时只清理固定根内未提交的陈旧运行目录,不触碰正式目录或其他用户文件。

8. 冻结后半程接管

完整 MKV 形成后,主机先关闭 CookieJar 并释放认证元数据,再以固定非秘密 argv 启动冻结 bridge:

<bridge-python> <bridge-script>
  --input <exact-batch-json>
  --yt-dlp <credential-free-yt-dlp>
  accept-browser-file
  --bvid BV1HA3o6oEJJ
  --media-file <fixed-internal-stage-candidate>
  --destination <authorized-destination>
  --ffprobe <ffprobe>

argv 不含 Cookie、token、signed URL 或网页对象。bridge 继续负责:输入文件稳定且无 companion partial、公共无凭据远端元数据、目标卷只读复制、FFprobe 同时有音视频、3133.95 s 时长一致性、源/正式双 SHA-256、CreateNew 媒体与 mapping、BaseException 回滚和暂存清理。600.133313 秒预览必须在正式发布前失败。

主机只解析 bridge 的固定 JSON 成功字段;失败 stdout/stderr 不原样进入扩展或日志。只有 exit 0、映射 BVID/bytes/hash/duration/acquisition_mode 全部匹配,且正式媒体和 mapping 均存在时返回 COMPLETE。成功后可清理主机自有的内部候选;若成功后清理失败,正式交付仍保留并返回静态 W_STAGING_RETAINED,下次启动仅在确认正式 mapping 已存在且哈希匹配时删除本主机运行目录。主机绝不删除或覆盖正式文件。

9. 失败、取消与重试

  • 页面证明、Cookie schema、认证元数据、DRM 或格式门禁失败:网络媒体写入为 0。
  • yt-dlp/FFmpeg 失败:终止本任务后代进程,清理内部运行目录,不调用 bridge。
  • bridge 失败:保留 bridge 的零/回滚正式输出合同,清理主机内部运行目录;扩展只收到固定 E_BACKHALF
  • KeyboardInterruptSystemExit、Native Host 断连、Chrome 关闭和未知 BaseException:先结束本任务后代、关闭内存 CookieJar、清理内部暂存,再传播为固定失败状态;不打印原对象。
  • 用户取消:与失败同样清理,不保留可恢复下载;扩展显示 CANCELED。
  • 重试:仅由用户点击触发;重新校验当前页面、重新读取当前 Cookie、创建全新随机运行目录。禁止读取旧 Cookie、旧 URL、旧 partial 或旧任务正文。
  • 已存在正式媒体或 mapping:在 Cookie 读取、网络和暂存创建前返回 E_EXISTS,无覆盖、无重复下载。

10. 实现文件边界

设计 PASS 后仅允许新增/修改下列范围:

  • 新增 dev/project-dev/bili_authenticated_extension/ 内最小静态扩展、Native Host、构建和当前用户安装脚本;
  • 新增对应 dev/project-dev/test/bili_authenticated_extension/
  • 新增一页用法;
  • 更新本事项开发账本和私有 worklog。

禁止修改:

  • dev/project-dev/bili_video_download_bridge.py
  • dev/project-dev/test/test_bili_video_download_bridge.py
  • 当前安装的扩展目录和 public reference snapshot
  • F:\video\青枫浦上Q、正式 ana-data、600 秒隔离预览
  • 其他开发员文件或相邻动态采集工具

11. 离线与合成测试门禁

实现审核前全部测试使用合成 Cookie/URL 哨兵、fixture、mock Chrome API、fake yt-dlp/FFmpeg/FFprobe/bridge 和 TemporaryDirectory;网络、Chrome、扩展安装、注册表、真实 Cookie、真实媒体及正式路径动作均为 0。

11.1 协议和权限

  • manifest 只含批准权限;无 downloads/storage/localhost/外部消息/content script。
  • Native Host manifest 只有一个精确 origin;占位符、通配符、第二 origin 失败。
  • page BVID/host/path、时长、readyState、尺寸、过期、DRM 各反例均在 cookies.getAll 和 native connect 前失败。
  • start 的未知字段、其他 BVID、非 canonical、外域 Cookie、超长值、重复 JSON key、过大 frame、任意路径/命令注入全部失败且外部动作 0。
  • status/cancel 不携带秘密;busy 第二任务外部动作 0。

11.2 秘密不泄露

使用唯一 synthetic sentinel 作为 Cookie、token、signed URL、header 和第三方异常文本;覆盖成功、metadata 失败、下载失败、FFmpeg 失败、bridge 失败、取消、BaseException 和断连。逐字节扫描 stdout/stderr、协议响应、配置、项目临时树、argv/env、日志、mapping fixture、异常和证据,sentinel 出现次数必须为 0。

把文件 API、NamedTemporaryFilemkstemp、yt-dlp CookieJar 和 subprocess 设为观测桩,证明 Cookie 只进入 io.StringIO,不存在 Netscape Cookie 文件、固定 Cookie 路径、token 文件、URL sidecar 或浏览器 profile 访问。关闭后 stream 长度为 0 且已关闭。

11.3 下载和业务门禁

  • fake yt-dlp 断言 exact URL、bestvideo+bestaudio/best、MKV、noplaylist、无 cache/metadata/thumbnail/subtitle/plugin、内部 HTTP 下载器和本地 FFmpeg 输入;签名 URL 不进入 argv。
  • authenticated metadata 的其他 id、entries/multi-P、live、DRM、缺视频、缺音频、600 秒 preview、无 duration 均失败且 bridge=0。
  • 唯一完整合成 AV 候选调用冻结后半程一次;bridge argv 只含固定非秘密参数;成功响应 basename、bytes/hash/duration 与 mapping 绑定。
  • companion partial、文件增长、多个候选、输出碰撞和预览时长在 bridge/发布前失败;正式输出 0。
  • success/failure/cancel/BaseException/host disconnect 后内部暂存与后代进程均为 0;成功后清理警告不删除正式文件。
  • 安装/卸载脚本仅在临时 HKCU provider 桩和临时目录运行,验证 exact origin、当前用户范围、CreateNew、路径/重解析点拒绝和重复运行 fail-closed。

11.4 UI 和全项目回归

  • mock chrome.cookies 返回 synthetic 值,断言只在有效用户点击后调用一次;side panel 关闭/重开只查询安全状态;重试重新取 Cookie。
  • 按钮序列固定为校验 → 添加 → 进行中 → 已完成,失败时唯一重试;自动启动、页面脚本直接启动和第二并发任务均禁止。
  • 冻结 bridge 原目标测试和 project owner 范围全回归必须 PASS;冻结文件 bytes/SHA-256 必须与基线一致。

12. 审核与真实验收顺序

  1. 本 V001 先提交 dev.reviewer.project 合并安全设计审核;HOLD 则只修设计,禁止实现。
  2. 设计 PASS 后按第 10 节实施,并提交同一审核链一次必要实现审核;审核只用离线/合成证据。
  3. 实现审核 PASS 后,由 case_analysis.video_downloader 按其真实动作授权安装项目自有扩展/Native Host,在目标页可见地点击一次完整任务。开发线程不代替下载员读取真实会话或写正式目录。
  4. 真实验收必须得到约 3133.95 s、同时含音视频、无 partial、SHA-256 与 exact canonical mapping 的完整原件;再交媒体处理员顺序转写。
  5. 若平台返回 DRM/CAPTCHA、无法在不泄露秘密下证明完整授权、Native Messaging 安全边界不可满足,或只得到 600 秒预览,则报告单一具体 blocker;不得放宽门禁或改用签名直链。

13. 设计通过标准

独立审核需要逐项确认:

  • 精确单 BVID/单账号范围没有泛化;
  • 当前 TXT 扩展和冻结 backhalf 均未被修改;
  • Cookie 与 signed URL 的内存生命周期、协议、日志、异常、yt-dlp、FFmpeg 和清理合同无持久化缺口;
  • Native Messaging 唯一 origin、无 loopback、无外部消息和固定路径配置构成闭合边界;
  • entitlement、DRM、multi-P、preview、partial、AV、时长、SHA-256、CreateNew 和 BaseException 均 fail-closed;
  • 测试能够用 synthetic sentinel 证明秘密不进入任何证据面;
  • 设计审核前所有真实外部动作为 0。

设计 PASS 仅授权离线最小实现,不授权真实 Cookie、Chrome 安装、真实下载、F:\video、正式 ana-data 或转写。