# 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 注册 `0`、`F:\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:///`,禁止占位符遗留、通配符或第二来源。 - 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. 组件和数据流 ```text 用户可见 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`:用户可见控制面; - `activeTab` 与 `scripting`:仅在用户点击时读取当前页的非秘密播放证明; - `cookies` 加 `https://www.bilibili.com/*` host permission:仅在用户点击添加/重试时读取该 URL 可发送的 Cookie; - `nativeMessaging`:连接唯一专用 Native Host。 不申请 `downloads`、`storage`、`debugger`、`webRequest`、`declarativeNetRequest`、`tabs` 通配访问或 `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 >= 1`、`videoWidth > 0`、`videoHeight > 0`; - `duration` 与 `3133.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=True`、`continuedl=False`、`overwrites=False`、`cachedir=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: ```text --input --yt-dlp accept-browser-file --bvid BV1HA3o6oEJJ --media-file --destination --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`。 - `KeyboardInterrupt`、`SystemExit`、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、`NamedTemporaryFile`、`mkstemp`、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` 或转写。