Skip to content

Security: chotgpt/cursor-usage-viewer

SECURITY.md

安全说明

数据流与用户触发

应用启动时只加载自身应用数据目录中已经落盘的账号索引、账号明细、最后额度快照和 Cursor 自动刷新设置,不读取 Cursor 数据库,也不自动发起网页登录。若 Cursor 自动刷新间隔大于 0 且已有账号,Tauri 应用进程可在任务到期时使用现有凭据续期并查询额度;窗口隐藏到托盘后仍继续,退出应用即停止。若用户没有关闭自动更新检查,应用可在界面就绪后独立访问本项目固定 GitHub Release updater endpoint;应用更新和额度刷新是两套独立设置,更新请求不携带任何 Cursor 账号凭据。

用户点击“导入本机当前 Cursor 账号”后,Rust 侧才以只读方式打开当前平台 Cursor 的 User/globalStorage/state.vscdb(Windows %APPDATA%/Cursor、macOS ~/Library/Application Support/Cursor、Linux ~/.config/Cursor),并只查询 docs/DECISIONS.md §D-003 列出的五个键。数据库不会被复制或修改。读取结果按账号身份合并进本应用存储;该导入操作本身不写回或切换 Cursor。

用户在账号卡片或列表点击 Play 后(docs/DECISIONS.md §D-033),应用按固定顺序对默认实例执行一键切号:首次事务注入默认 state.vscdb(精确七键语义:四个必写键始终覆盖,Refresh Token、套餐、订阅状态缺失时保留旧值,邮箱缺失时写 unknown),持久化当前账号与默认绑定,校验启动路径,关闭已验证的默认 Cursor 实例(最多 20 秒),重新读取账号并二次注入,再以默认目录和 --new-window 启动。Windows 关闭使用 taskkill /PID … /T /F,可能丢失未保存内容;访问被拒时可对已验证的 Cursor PID 发起 UAC 提权重试(最多 32 个 PID,白名单仅限当前配置的 Cursor 可执行路径与默认 profile)。路径缺失时首次注入与绑定已完成,按软成功返回并打开路径恢复弹层,保存后只重试默认实例启动,不重新提交前端凭据。错误、日志、DTO 与事件不得包含 Token、邮箱、SQL 值或数据库内容。

用户主动粘贴 Access Token、单行 <user_id>::<accessToken> 网页 Token 或 Cockpit Tools JSON 并提交时,敏感输入会短暂经过 WebView 和 Tauri IPC;提交后输入框立即清空。网页 Token 只接受一个 :: 分隔符,Rust 会校验 user_id 与 JWT sub 身份一致并只持久化裸 Access Token;包装前缀不落盘。用户也可在本机导入页主动选择单个 .json 文件;Rust 只对该精确路径执行扩展名、8 MiB 大小和限长读取,再复用相同的最多 500 账号解析与持久化链路。应用不自动扫描 Cockpit Tools 目录,也不获得通用文件系统权限。

用户点击单账号、选中账号或全部账号刷新,或启用的自动刷新任务到期后,应用按 docs/DECISIONS.md §D-012、§D-020、§D-022 访问固定 Cursor 第一方端点。批量刷新逐账号顺序执行,手动与自动动作共用单并发协调;一个账号或可选数据源失败不影响其他账号,下个周期仍可重试。关闭自动刷新时不会产生后台 Cursor 请求。

网页登录只在用户从添加账号弹层主动开始后运行。应用使用随机 challenge/uuid 打开受限的 Cursor 登录页,并由 Rust 每 2 秒轮询一次受限的 Cursor auth/poll,最长 300 秒,用户可取消。Token、PKCE verifier 和完整轮询 URL 不得进入普通 DTO、日志、错误、DOM 持久化或视觉快照;成功后前端只接收脱敏账号视图。账号落盘后,应用会对该新账号立即执行一次与手动刷新相同的额度查询(docs/DECISIONS.md §D-023);若此时有其他刷新正在进行或查询失败,登录仍视为成功并返回已保存的视图。本机导入、粘贴 Token 和 JSON 文件导入只落盘,不触发网络请求。

固定网络白名单

生产 Provider 只允许以下 HTTPS 方法与精确路径,禁止重定向、片段和任意 URL。除网页登录专用验证器允许的固定参数外,其他端点仍禁止查询参数:

POST https://api2.cursor.sh/oauth/token
POST https://api2.cursor.sh/aiserver.v1.AuthService/GetUserMeta
GET  https://api2.cursor.sh/auth/full_stripe_profile
GET  https://api2.cursor.sh/auth/stripe_profile
GET  https://cursor.com/api/usage-summary
POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetSandUsageStatus
POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetAggregatedUsageEvents
POST https://cursor.com/api/dashboard/get-sand-access-status
GET  https://api2.cursor.sh/auth/poll?uuid=<generated>&verifier=<secret>

系统浏览器只允许打开 https://cursor.com/loginDeepControl,并且只允许应用生成的 challenge、uuid 和固定 mode=login 参数。auth/poll 只允许应用生成的 uuid、verifier 参数;二者均使用专用验证器,不接受用户提供的主机、路径或附加参数。

OAuth Token 端点只在手动或已启用的自动刷新中,Access Token 不可解析或五分钟内过期时使用;失败后继续尝试旧 Access Token。usage-summary 是 Total、Auto + Composer、API、On-Demand 和计费周期的唯一实时真源。Sand 用量端点使用 Access Token 的 Bearer 认证、Connect 协议版本和 {} 请求体;Sand 资格端点使用已经确认的 Origin: https://cursor.com 与 WorkOS Cookie。GetAggregatedUsageEvents 使用与 Sand 用量相同的 Bearer/Connect 请求头,请求体只含由 Sand 周期起点和当前时间派生的 startDate/endDate 毫秒字符串,用于汇总 Bot 周期内全部模型的 totalCents;只有 Sand 用量阶段成功并返回周期起点时才调用。三者都是独立可选数据源。

旧的 DashboardService/GetCurrentPeriodUsage 已由 D-012 取消,不得继续加入生产白名单或作为 Free 账号 fallback。

应用更新网络与签名

应用更新与 Cursor Provider 使用独立的网络边界。更新检查只允许访问本项目 GitHub Releases 下配置的 latest-{{target}}.json / latest.json 及其指向的本项目 Release assets;不得接受用户输入的任意更新源,不得把 Cursor Token、Cookie、邮箱、账号 JSON 或设备数据库内容放入更新请求。

WebView 的 CSP 保持 connect-src 'none';Cursor Provider 和 updater 网络都由受限 Rust/Tauri 能力执行,前端不得直接 fetch 外部地址。浏览器手动下载兜底只可打开本项目固定 GitHub Release 页面。

所有应用内可安装资产都必须通过内嵌 Tauri updater 公钥验证签名。GitHub 上的 SHA256 和 artifact attestation 用于用户与发布流程复核,不能替代应用内签名验证。签名不匹配、目标/版本不一致、清单无效或 URL 不属于本项目发布链路时必须停止,不得提供“仍然安装”选项。

Tauri updater 私钥和密码只允许存于 GitHub Actions Secrets 与离线加密备份;不得提交到仓库、写入应用数据、Actions cache/artifact 或日志。fork PR 不得获得签名 Secrets。

Windows 与 macOS 首版不做操作系统代码签名/公证,因此可能出现 SmartScreen 或 Gatekeeper 提示;文档必须如实说明。Tauri updater 签名和操作系统代码签名用途不同,不能互相替代。

Linux AppImage 使用标准 Tauri updater。deb/rpm 只有在用户明确点击安装、目标包已下载并通过签名验证后,才可调用固定包管理器命令触发系统提权;用户取消提权是正常失败。未知安装类型、未知架构或任意包路径不得执行安装命令。

本地持久化与导出

应用按用户确认的 Cockpit 兼容模式,在 Tauri app_data_dir() 下保存轻量账号索引及每账号独立 JSON 明细。账号明细和 .bak 包含明文 Access Token、Refresh Token、账号资料和最后额度快照;不提供 DPAPI 或额外加密层。能够读取当前 Windows 用户应用数据目录的其他进程也可能读取这些凭据。

普通列表、刷新、自动刷新事件、网页登录状态、筛选和删除命令只返回脱敏 DTO,不含 Access Token、Refresh Token、Cookie、verifier 或原始认证对象。只有用户主动点击完整 JSON 导出时,包含明文 Token 的内容才可进入导出预览。预览按 Cockpit 行为默认遮罩全部字符串,并允许用户主动显隐、复制或保存;账号页的可折叠说明和导出弹窗文案必须明确内容敏感,但不增加额外确认步骤。导出弹窗中的“复制网页 Token”只在前端把同一份导出 JSON 转换为 <user_id>%3A%3A<accessToken> 形式写入剪贴板(docs/DECISIONS.md §D-031),不新增命令、端点、权限或落盘路径。

删除账号时必须删除该账号主 JSON 与账号 .bak,并清除可能保留已删除摘要的索引备份;不得让删除后的明文 Token 残留在账号备份文件中。索引仍可由现存账号明细重建。

响应脱敏

Cursor 请求头必须标记为敏感。应用不记录 Token、Cookie、邮箱、请求体或响应正文。HTTP/JSON 错误只可返回状态码、Content-Type、响应字节长度和“空体 / HTML / JSON 形态 / 其他”分类等结构化证据,不得包含正文片段。更新错误同样必须脱敏、截断,不得记录 Secrets、环境变量、用户名路径、任意 manifest 正文或包管理器完整输出。

外部字段缺失保持未知,不转换为 0。核心额度请求失败时保留上一次核心快照并记录脱敏错误;Sand 用量、资格或周期消费失败时保留各自独立的上次成功结果,不得丢弃核心额度。周期消费响应缺少 aggregations 数组时保持未知,不显示为 $0。

测试边界

SQLite 测试使用临时 fixture 数据库;存储测试使用临时目录;Cursor 网络测试只用 mock transport/server 和假 JWT。updater 测试使用隔离清单、测试 keypair 和假安装包,必须覆盖篡改后签名失败。测试、构建与视觉验证不得读取本机真实 Cursor 数据库、应用数据账号、剪贴板或真实 Token,不得发送真实 Cursor 请求,也不得控制用户现有窗口、鼠标或进程。

已知传递依赖风险

Linux 的 Tauri v2 / GTK3 栈传递依赖 glib 0.18.5,受 GHSA-wrw7-89jp-8q8g(RUSTSEC-2024-0429)影响。缺陷位于 glib::VariantStrIter / array_iter_str();当前项目及其消费方没有调用该 API,因此没有已知可达路径。它仍是真实的上游未定义行为风险,不是误报:glib 0.18 已 EOL 且维护者不会发布 backport,正式修复版 0.20 又不兼容当前 GTK3 依赖链。Dependabot 告警按 docs/DECISIONS.md §D-027 记录为可容忍风险;Tauri 提供 GTK4/glib ≥ 0.20 路径、可信兼容 backport 出现,或依赖图新增该 API 调用时必须重新评估。

明确不做

  • 除默认实例 Play 一键切号(docs/DECISIONS.md §D-033:用户点击触发、精确七键注入、默认 profile 进程关闭/重启、受限 UAC 重试)外,不切换 Cursor 当前账号、不把 Token 注入 Cursor、不写回 Cursor 数据库、不启动 Cursor、不多开;
  • 第三方 OAuth、任意登录/轮询 URL、启动时自动发起网页登录或启动时读取本机 Cursor;
  • 通用文件读取、自动扫描 Cockpit Tools 数据、遥测、崩溃上报、远程日志或云同步;
  • 任意 URL 请求或访问第三方账号服务器。

报告安全问题

请勿在公开问题中粘贴 Token、Cookie、真实邮箱、完整数据库、账号明细或未经脱敏的响应。报告应包含最小复现步骤、受影响版本和已脱敏证据。

There aren't any published security advisories