Skip to content

关于协议逆向进度:字段核对、置信度,以及目前还有哪些未解决 #21

Description

@xelr233

背景

为了核对协议字段,我把 command-code npm 包(当前 1.53.0)的反混淆结果过了一遍。esbuild 打包 + keepNames,函数名以字符串形式保留,所以几个关键函数基本是可读的:buildCommandAuthHeaders、buildServerConfig、createModelClient、toWirePermissionMode、toWireThreadId、readStructure、buildMachineFingerprint、recordCliFingerprint。

有几处和本仓库当前实现不一致,想先和你对一下口径,再问进度。

观察到的一些差异

1. x-co-flag 在 CLI 里不存在

请求头名集中在一张常量表里(源码原文):

wy = {
  OAUTH_TOKEN: "x-oauth-token",           OAUTH_PROVIDER: "x-oauth-provider",
  PROJECT_SLUG: "x-project-slug",         TASTE_LEARNING: "x-taste-learning",
  CLI_ENVIRONMENT: "x-cli-environment",   CLI_VERSION: "x-command-code-version",
  OSS_PRIMARY_PROVIDER: "x-oss-primary-provider",
  SESSION_ID: "x-session-id",             CMD_ZDR: "x-cmd-zdr",
  PROVIDER_DEEPSEEK_INTERNAL: "x-cmd-provider-deepseek-internal"
}

没有 x-co-flag。buildCommandAuthHeaders 依次下发 Content-Type / User-Agent / 上表各项 / Authorization,也没有它。

2. User-Agent 是 cli

同文件常量 vy = "cli",buildCommandAuthHeaders 里 "User-Agent": vy。另外 recordCliFingerprint(指纹预请求)用的 e.userAgent ?? lb,而 lb = vy —— 也就是预请求同样发 cli,而本仓库的预请求目前不带 UA。

3. 信封里的 threadId 是真字段,且校验是 UUID

function toWireThreadId(e) { return Se.uuid().safeParse(e).success ? e : undefined; }

非 UUID 直接返回 undefined,键被 JSON.stringify 丢掉。而 createModelClient 里实际发的是 body.threadId 与 x-session-id 同值。本仓库这里目前是 buildCcRequest 里的 const threadId = newThreadId(),算完没进入请求体(死代码);我已在自己 fork 里改成同值下发,非 UUID 则省略。

4. memory / taste / skills 三个都是字面 null

body: { config: d, memory: null, taste: null, skills: null,
        permissionMode: u, threadId: toWireThreadId(t.threadId),
        mode: t.mode, promptCache: t.promptCache, params: {...} }

本仓库是 memory: null, taste: null, skills: '' —— skills 是空串而非 null。

5. 信封已经长到 9 键

抓包规格里是 7 键;1.53.0 多了 mode 与 promptCache。任何更早抓取的规格都已经过期 —— 这可能比任何单个字段都值得记一笔,因为纯抓包路线会随上游版本静默失配。

6. config.environment 是一个平台词

async function buildServerConfig(e) {
  const o = t.platform(), s = t.cwd();
  ...
  return { workingDir: s, date: r, environment: o, structure: n, isGitRepo: true, ... };
}

本仓库发的是 ${process.platform}-${process.arch}, Node.js ${version}。

7. structure 的精确算法

readdir(cwd).filter(x => !x.startsWith(".") && !Uw.has(x)).sort()
// Uw = new Set(["node_modules","dist","build",".git",".svn",".hg","coverage",
//                ".nyc_output",".cache","tmp","temp",".next",".nuxt","out"])

单层、滤隐藏文件、15 项黑名单、排序。另外 gitStatus 在 git status --porcelain 为空时填字面量 "Working tree clean"。

8. x-taste-learning 是用户设置,不是常量

[wy.TASTE_LEARNING]: e.tasteLearningEnabled.toString()

来自 isTasteLearningEnabled()(读用户配置 + 项目覆盖)。本仓库固定 'false' —— 值本身合法,但要知道它反映的是"该 CLI 用户的设置",不是协议常量。

9. 指纹只以哈希离开本机,且每进程只报一次、可关闭

components 里 machineIdHash / macHashes / osUserHash / hostnameHash / gitEmailHash 全是哈希;明文只有 platform/arch/osRelease/cpuModel/cpuCount/memGiB/isContainer/timezone。上报口是:

function recordCliFingerprint(e) { if (!cb && (cb = true, isTelemetryEnabled())) { ... } }
function isTelemetryEnabled() {
  if (process.env.DO_NOT_TRACK === "1" || === "true") return false;
  if (isLocalOnly()) return false;
  return config.telemetry !== false;
}

即 DO_NOT_TRACK=1 会让真 CLI 自己就不报指纹。

10. 客户端里没有任何检测/风控逻辑

grep 全 bundle:"Proxy use detected" 命中 0 次,minVersion、upgrade_required 同样 0 次。逆向能确定"该发什么",但确定不了"服务端在查什么"。

想请教的问题

  1. 现有字段值的来源分别是什么? 哪些是反混淆 CLI 得到的、哪些是抓包、哪些是参考其它项目或试错?想标清楚置信度——我这轮只能证明"和 1.53.0 不一致",证明不了"不一致就有后果"。

  2. x-co-flag 是保留还是可以去掉? 如果它来自早期某个 CLI 版本、或来自其它项目的相互引用,可能值得在代码里注明出处;如果它有实际作用(哪怕只是对照实验的结果),那也是一条重要信息。

  3. x-session-id / threadId 的轮换节奏有实测结论吗? 现在的实现是 per-key 12h + 1h 抖动,且 threadId 从 sessionId 派生。真 CLI 那边我看到的是 x-session-id 随会话轮换、threadId 与它同值,但"一个会话多久换一次"以及服务端是否会比对两者,我这边没有证据。

  4. Proxy use detected 或者 upgrade_required 这条路径,有人实际触发过吗? 目前我在公开渠道只找到一份独立报告,触发条件不明。这条大概是整个项目最关键的未知项。

  5. 进度问题:目前哪些是已经确定、不打算再动的,哪些还在解决中? 特别是:

    • 指纹/生命周期这套预请求,是"已确认必要"还是"宁可信其有"?
    • 有没有你试过、结论是没用的方案?(负结果对后来者比正结果更省时间)
    • 有没有已知的、暂时不打算修的不一致?
  6. 协议变更怎么追? 1.53.0 已经是 426 个版本里的最新一个,信封从 7 键变 9 键。本仓库的 x-command-code-version 是从 npm registry 动态拉的(这点比大多数项目的硬编码快照好),但信封结构本身目前靠什么发现变化?

可提供的东西

如果你觉得有用,我可以:

  • 把我的 fork 里那两处(UA + threadId 同值)开成 PR;
  • 或者先只把上面这份字段核对整理成一份文档(含每个字段对应的 CLI 源码片段),放进仓库作为"协议真值参考",供后续版本对比用。

我这边的核对方式(下载 npm tarball → 定位函数名 → 提取片段)是纯本地静态阅读,不含任何对服务端的探测。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions