Skip to content

Operator API Security zh

Nemu-x edited this page Jul 7, 2026 · 1 revision

ClashFest Operator API —— 安全

🌐 English · Русский · 中文

Operator API 是一个受信任边界约束的扩展面。客户端将每个值都视为来自网络的不可信输入,并在其触及 UI、主题或磁盘之前施加严格校验。

本文描述客户端保证什么、不保证什么,以及我们明确防御哪些攻击。

威胁模型

相关攻击者类别:

  1. 恶意运营方 —— 控制订阅 URL 响应。可能发送恶意响应头(超大值、载荷、恶意 URL)。我们像对待任何外部输入一样对待它。
  2. 网络攻击者 —— 在受损网络上拦截订阅 HTTP 响应(罕见,因订阅走 HTTPS)。
  3. 运营方图床被攻陷 —— 徽标 CDN 被接管,开始提供恶意图片 / 重定向到内部服务。
  4. 应用商店审核者 / 监管者 —— 需要看到「品牌」无法悄然把应用变成另一个应用(身份改变仅限用户屏幕所见,而非应用行为)。

注:同时是订阅提供方的运营方,本就能在此 API 之外滥用许多东西(代理选择、路由规则)。品牌只是多了几个旋钮 —— 这些旋钮各自仍须安全。

客户端校验什么

字符串

  • 去除首尾空白
  • 去除控制字符(U+0000U+001FU+007F,字段允许时保留 \n / \r / \t
  • 截断到字段最大长度(见响应头参考
  • 解码 base64: 前缀
  • 以上处理后,空白 → 字段视为不存在

URL

仅当值以下列之一开头才接受:

  • https://tg://mailto:t.me/(自动升级为 https://t.me/

品牌 URL 拒绝 http://。唯一例外是订阅 URL 本身的元数据探测,用户已明确选择明文订阅。

URL 还会通过 looksLikeUrl 检查(长度、无内嵌空白)。

十六进制颜色

  • 仅正则 ^#[0-9A-Fa-f]{6}$ —— 不支持 #RGBrgba()、命名颜色。
  • 客户端计算相对表面色的 WCAG 亮度。若对比度低于 3:1(AA「大文本」最低值),拒绝该颜色,使用默认主题强调色。

布尔 / 枚举 / 整数

  • 布尔:true/1/yes/on → true;false/0/no/off → false;其他 → 响应头视为不存在。
  • 枚举:仅接受文档中的值;未知值忽略。
  • Subscription-Userinfo 流量:Long ≥0;expire= 须在 [0, now + 10 年],否则忽略。X-Brand-Max-Devices:1–999。

图片(X-Brand-Logo-URLX-Brand-Logo-Light-URL

这是风险最高的字段 —— 我们从运营方选定的 URL 拉取任意字节。防御:

传输

  • 仅 HTTPS。 http:// 在构建请求前即拒绝。
  • 不重定向到私有网络。 每一跳前,解析出的 IP 会与以下比对:
    • 10.0.0.0/8172.16.0.0/12192.168.0.0/16127.0.0.0/8169.254.0.0/16
    • ::1/128fc00::/7fe80::/10
    • 以及其他 RFC1918 / RFC4193 / RFC6890 保留空间
  • 连接 + 读取超时:各 15 秒 · 最大重定向:3
  • 绑定设备自身 DNS 解析器(拉取期间无系统覆盖)

响应

  • Content-Type 须为 image/pngimage/webpimage/jpeg。其他(含 image/svg+xmlimage/giftext/html)一律拒绝。
  • Content-Length(若存在)须 ≤ 512KB。若缺失,读取上限 512KB,超出即中止下载。
  • 字节先以 bounds-only 模式由 BitmapFactory.decodeByteArray 解析;仅当 outWidth × outHeight × 4 ≤ 4MB(最大约 1024×1024)时才二次解码。
  • 动图(animated WebP)仅解码首帧。

存储

  • 徽标缓存于 <filesDir>/brand/<sha256(url)>(应用私有)
  • 原子写入:tmp + rename
  • 清除时机:所属订阅被删除;同订阅收到不同 URL 的新徽标
  • 从不写入外部存储,其他应用不可访问

为什么不支持 SVG

SVG 可携带类 XSS 载荷(<script>、指向远程资源的 xlink:href、CSS 导入、foreignObject HTML)。即便更安全的解析也无法绝对防止 DoS(billion-laughs、几何爆炸)。徽标是旗帜位图;PNG/WebP 足以覆盖用例而无 SVG 攻击面。

客户端校验什么

  • 徽标是否真代表该运营方? 否 —— 我们不验证品牌所有权。
  • 运营方网站是否真是其所有? 同上。
  • 品牌名是否与面板一致? 否 —— 自由文本。

这些属运营方信任问题,而非客户端安全问题。用户添加此订阅时就已选择信任它。

身份改变可见

按设计,品牌绝不以用户无法察觉的方式悄然改变应用身份

  • Android 启动器图标与包名从不改变。商店审核者始终能识别该应用。
  • 关于页始终显示 <brand> — powered by ClashFest(不可翻译、固定)。
  • 品牌激活时,设置中会有「Reset branding」,一键恢复默认身份。

品牌何时重置

品牌缓存(名称、徽标、强调色、链接)在以下情况被清除:

  • 活动配置文件被移除(且无其他配置文件)
  • 用户在设置中点击「Reset branding」
  • 同订阅的后续拉取返回的响应头不再携带品牌值(清除品牌是运营方侧操作,而非永久卡住)

我们在应用升级时重置品牌 —— 它会跨版本升级存留。

频率 / 重试

  • 徽标拉取每个订阅更新周期最多一次
  • 失败的拉取记忆 1 小时,避免重试风暴
  • SharedPreferences 中的「活动品牌」缓存是更新之间的权威来源:断网不会在会话中途剥离品牌

报告

若你在 Operator API 或其解析器中发现安全问题,请遵循仓库根目录顶部的安全策略。

Clone this wiki locally