You asked for all local code to be committed before the broader telemetry-removal pass. This commit snapshots the current bridge/session ingress changes together with the local audit documents so the next cleanup can proceed from a stable rollback point. Constraint: Preserve the exact local worktree state before the telemetry-removal refactor begins Constraint: Avoid mixing this baseline snapshot with the upcoming telemetry deletions Rejected: Fold these staged changes into the telemetry-removal commit | Would blur the before/after boundary and make rollback harder Confidence: medium Scope-risk: moderate Reversibility: clean Directive: Treat this commit as the pre-removal checkpoint when reviewing later telemetry cleanup diffs Tested: Not run (baseline snapshot commit requested before the next cleanup pass) Not-tested: Runtime, build, and typecheck for the staged bridge/session changes
424 lines
15 KiB
Markdown
424 lines
15 KiB
Markdown
# `free-code-main` 本地系统信息外发移除实现报告
|
||
|
||
- 分析时间: 2026-04-03
|
||
- 对照文档: `docs/local-system-info-egress-audit.md`
|
||
- 分析对象: `/Users/yovinchen/Downloads/free-code-main`
|
||
- 对照基线: `/Users/yovinchen/project/claude`
|
||
- 分析方式: 静态代码审计 + 关键链路比对 + 同名文件差异核查
|
||
- 说明: 本报告只基于源码静态分析,不包含运行时抓包或服务端验证。
|
||
|
||
## 结论摘要
|
||
|
||
结论是: **`free-code-main` 只“部分移除”了审计文档里的本地系统信息外发链路。**
|
||
|
||
更准确地说,它做的是:
|
||
|
||
1. **把 telemetry / analytics / OTel 相关外发出口失活了**
|
||
- Datadog
|
||
- Anthropic 1P event logging
|
||
- OTel 事件与 metrics/tracing 初始化
|
||
- GrowthBook 远程评估链路也被间接短路
|
||
|
||
2. **但没有把“所有本地信息外发”都移除**
|
||
- 模型请求里的环境/项目上下文注入仍在
|
||
- Feedback 上传仍在
|
||
- Transcript Share 仍在
|
||
- Remote Control / Bridge 上传 `hostname`、目录、分支、git remote URL 的链路仍在
|
||
- Trusted Device 注册仍在
|
||
- `/insights` 的 ant-only 上传逻辑仍在
|
||
|
||
3. **移除方式不是“彻底删代码”,而是“保留兼容接口 + 启动链路短路 + sink/no-op stub 化”**
|
||
- 这意味着仓库里仍然保留了不少采集/导出代码。
|
||
- 但默认运行时,关键出口函数已经被改成空实现,导致这些链路无法真正发出请求。
|
||
|
||
因此,如果问题是:
|
||
|
||
> `free-code-main` 是否已经把 `docs/local-system-info-egress-audit.md` 中描述的“本地系统信息外发”整体移除?
|
||
|
||
答案是:
|
||
|
||
**没有整体移除,只移除了其中“遥测/观测”这一类外发;产品主链路里的上下文外发和若干用户触发上传链路仍然存在。**
|
||
|
||
## 对照矩阵
|
||
|
||
| 审计项 | `free-code-main` 状态 | 结论 |
|
||
| --- | --- | --- |
|
||
| F1 模型请求 system prompt / user context | 未移除 | 默认仍会把 cwd、git 状态、CLAUDE.md、日期,以及 prompts 里的平台/壳层/OS 版本注入到模型请求 |
|
||
| F2 Datadog analytics | 已移除 | Datadog 初始化与上报函数被 stub 成 no-op |
|
||
| F3 Anthropic 1P event logging | 已移除 | 1P logger 整体改为空实现,启用判断恒为 `false` |
|
||
| F4 GrowthBook remote eval | 实际已失活 | 依赖 `is1PEventLoggingEnabled()`,而 1P 已被硬关,默认不会创建 GrowthBook client |
|
||
| F5 Feedback | 未移除 | 用户触发后仍会 POST 到 `claude_cli_feedback` |
|
||
| F6 Transcript Share | 未移除 | 用户触发后仍会 POST 到 `claude_code_shared_session_transcripts` |
|
||
| F7 Remote Control / Bridge | 未移除 | 仍会采集并上送 `hostname`、目录、分支、git remote URL |
|
||
| F8 Trusted Device | 未移除 | 仍会注册 `Claude Code on <hostname> · <platform>` |
|
||
| F9 OpenTelemetry | 已移除 | telemetry 初始化与 `logOTelEvent()` 都被改成 no-op |
|
||
| F10 `/insights` 内部上传 | 未移除 | ant-only S3 上传逻辑仍保留 |
|
||
|
||
## 关键判断
|
||
|
||
这次比对里最重要的判断有两个:
|
||
|
||
1. **`README.md` 里的 “Telemetry removed” 只覆盖了“遥测/观测”语义,不等于“所有本地信息外发已删除”。**
|
||
2. **`free-code-main` 的移除策略主要是“切断出口”,而不是“删除所有采集代码”。**
|
||
|
||
这也是为什么你会看到:
|
||
|
||
- `src/services/analytics/metadata.ts` 这类环境信息构造代码还在
|
||
- `src/utils/api.ts` 里上下文统计代码还在
|
||
- `src/services/analytics/firstPartyEventLoggingExporter.ts`、`src/utils/telemetry/bigqueryExporter.ts` 这类导出器文件也还在
|
||
|
||
但是:
|
||
|
||
- 事件 sink
|
||
- telemetry bootstrap
|
||
- OTel event logging
|
||
- Datadog / 1P logger 初始化
|
||
|
||
都已经被改成空实现或被前置条件短路掉了。
|
||
|
||
## 已移除部分: 实现方式分析
|
||
|
||
### 1. Analytics 公共入口被改成 compatibility boundary + no-op
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/services/analytics/index.ts:4-40` 明确写到:
|
||
|
||
- “open build intentionally ships without product telemetry”
|
||
- 保留模块只是为了不改动现有调用点
|
||
- `attachAnalyticsSink()`、`logEvent()`、`logEventAsync()` 都是空实现
|
||
|
||
这意味着:
|
||
|
||
- 各业务模块里仍然可以继续 `import { logEvent }`
|
||
- 但这些调用不会再入队、不会再挂 sink、也不会再向任何后端发送
|
||
|
||
对照 `/Users/yovinchen/project/claude/src/services/analytics/index.ts`,当前工作区版本还保留:
|
||
|
||
- 事件队列
|
||
- `attachAnalyticsSink()` 的真实绑定
|
||
- `logEvent()` / `logEventAsync()` 的真实分发
|
||
|
||
所以这里是非常明确的“出口 stub 化”。
|
||
|
||
### 2. Datadog 被直接 stub 掉
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/services/analytics/datadog.ts:1-12` 中:
|
||
|
||
- `initializeDatadog()` 直接返回 `false`
|
||
- `shutdownDatadog()` 空实现
|
||
- `trackDatadogEvent()` 空实现
|
||
|
||
而对照 `/Users/yovinchen/project/claude/src/services/analytics/datadog.ts:12-140`,基线版本仍然保留:
|
||
|
||
- Datadog endpoint
|
||
- 批量缓冲
|
||
- `axios.post(...)`
|
||
|
||
因此 F2 可以判定为**已移除**。
|
||
|
||
### 3. 1P event logging 被整体空实现
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/services/analytics/firstPartyEventLogger.ts:1-48` 中:
|
||
|
||
- `is1PEventLoggingEnabled()` 恒为 `false`
|
||
- `logEventTo1P()` 空实现
|
||
- `initialize1PEventLogging()` 空实现
|
||
- `reinitialize1PEventLoggingIfConfigChanged()` 空实现
|
||
|
||
这和基线 `/Users/yovinchen/project/claude/src/services/analytics/firstPartyEventLogger.ts:141-220` 中真实存在的:
|
||
|
||
- `getEventMetadata(...)`
|
||
- `getCoreUserData(true)`
|
||
- OTel logger emit
|
||
|
||
形成了直接对照。
|
||
|
||
需要注意的是:
|
||
|
||
- `src/services/analytics/firstPartyEventLoggingExporter.ts` 文件仍然存在
|
||
- 里面仍保留 `/api/event_logging/batch` 的完整实现
|
||
|
||
但由于 logger 初始化入口已经空了,这个 exporter 在默认路径上已经不会被接上。
|
||
|
||
因此 F3 的移除方式属于:
|
||
|
||
**保留 exporter 源码,但把“上游 logger/provider 初始化”整体切断。**
|
||
|
||
### 4. Analytics sink 初始化被清空,启动调用点保留
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/services/analytics/sink.ts:1-10` 中:
|
||
|
||
- `initializeAnalyticsGates()` 空实现
|
||
- `initializeAnalyticsSink()` 空实现
|
||
|
||
但启动链路并没有删调用点:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/main.tsx:83-86,416-417` 仍然 import 并调用 `initializeAnalyticsGates()`
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/setup.ts:371` 仍然调用 `initSinks()`
|
||
|
||
这说明作者的思路不是“到处改业务调用点”,而是:
|
||
|
||
**保留启动顺序与依赖图,统一在 sink 层面把行为变空。**
|
||
|
||
### 5. OTel 初始化被显式短路
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/entrypoints/init.ts:207-212` 直接把:
|
||
|
||
- `initializeTelemetryAfterTrust()`
|
||
|
||
改成了立即 `return`。
|
||
|
||
同时:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/utils/telemetry/instrumentation.ts:1-24`
|
||
- `bootstrapTelemetry()` 空实现
|
||
- `isTelemetryEnabled()` 恒为 `false`
|
||
- `initializeTelemetry()` 返回 `null`
|
||
- `flushTelemetry()` 空实现
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/utils/telemetry/events.ts:1-12`
|
||
- `logOTelEvent()` 空实现
|
||
- 用户 prompt 内容默认只会被 `redactIfDisabled()` 处理成 `<REDACTED>`
|
||
|
||
而调用点仍保留:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/main.tsx:2595-2597` 仍会调用 `initializeTelemetryAfterTrust()`
|
||
- 多个业务模块仍会调用 `logOTelEvent(...)`
|
||
|
||
所以 F9 的移除方式也是:
|
||
|
||
**不删调用点,只把 telemetry bootstrap 和 event emit 统一改成 no-op。**
|
||
|
||
### 6. GrowthBook 不是“彻底删文件”,而是被前置条件短路
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/services/analytics/growthbook.ts:420-425`:
|
||
|
||
- `isGrowthBookEnabled()` 直接返回 `is1PEventLoggingEnabled()`
|
||
|
||
而 1P 在 `firstPartyEventLogger.ts:26-27` 中已经被硬编码为 `false`。
|
||
|
||
继续往下看:
|
||
|
||
- `growthbook.ts:490-493` 在 client 创建前就会因为 `!isGrowthBookEnabled()` 返回 `null`
|
||
- `growthbook.ts:685-691`、`748-750` 会在取 feature value 时直接返回默认值
|
||
|
||
这意味着从当前源码推断:
|
||
|
||
- 默认路径不会创建 GrowthBook client
|
||
- 默认路径不会执行 remote eval 网络请求
|
||
- 默认路径不会把 `deviceID/sessionId/platform/org/email` 发出去
|
||
|
||
所以 F4 应该判定为:
|
||
|
||
**远程评估外发链路实际上已失活。**
|
||
|
||
这里有一个值得单独记录的点:
|
||
|
||
- `README.md:58-64` 写的是 “GrowthBook feature flag evaluation still works locally but does not report back”
|
||
- 但从当前代码看,更准确的说法应该是:
|
||
- **默认的远程评估链路已经被短路**
|
||
- 留下的是兼容性结构和本地 override/cache 框架
|
||
|
||
这条判断是**基于源码的推断**。
|
||
|
||
### 7. 本地采集代码仍有残留,但最终不会出网
|
||
|
||
这部分很关键,容易误判。
|
||
|
||
`free-code-main` 不是把所有采集逻辑都删掉了。典型例子:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/services/analytics/metadata.ts:574-740`
|
||
- 仍会构造 `platform`、`arch`、`nodeVersion`、`terminal`、Linux distro、`process.memoryUsage()`、`process.cpuUsage()`、repo remote hash 等元数据
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/utils/api.ts:479-562`
|
||
- 仍会收集 `gitStatusSize`、`claudeMdSize`、项目文件数、MCP tool 数量
|
||
- 最后仍调用 `logEvent('tengu_context_size', ...)`
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/main.tsx:2521-2522`
|
||
- 启动时仍会执行 `logContextMetrics(...)`
|
||
|
||
但由于 `src/services/analytics/index.ts:28-38` 中 `logEvent()` 已经是空实现,这些数据虽然可能仍在本地被计算,但不会从该链路继续发出。
|
||
|
||
所以更准确的评价是:
|
||
|
||
**移除的是 egress,不是所有 collection 语句。**
|
||
|
||
## 未移除部分: 逐项核对
|
||
|
||
### F1. 默认模型请求上下文外发未移除
|
||
|
||
这部分在 `free-code-main` 里仍然存在,而且关键文件与基线高度一致。
|
||
|
||
直接证据:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/constants/prompts.ts:606-648`
|
||
- `computeEnvInfo()` 仍拼接:
|
||
- `Working directory`
|
||
- `Is directory a git repo`
|
||
- `Platform`
|
||
- `Shell`
|
||
- `OS Version`
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/constants/prompts.ts:651-709`
|
||
- `computeSimpleEnvInfo()` 仍拼接:
|
||
- `Primary working directory`
|
||
- `Platform`
|
||
- `Shell`
|
||
- `OS Version`
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/context.ts:36-109`
|
||
- `getGitStatus()` 仍读取:
|
||
- 当前分支
|
||
- 默认分支
|
||
- `git status --short`
|
||
- 最近 5 条提交
|
||
- `git config user.name`
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/context.ts:116-149`
|
||
- `getSystemContext()` 仍把 `gitStatus` 放入上下文
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/context.ts:155-187`
|
||
- `getUserContext()` 仍把 `CLAUDE.md` 内容和日期放入上下文
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/utils/api.ts:437-474`
|
||
- `appendSystemContext()` / `prependUserContext()` 仍会把这些内容拼进消息
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/query.ts:449-451,659-661`
|
||
- 查询时仍将这些上下文交给模型调用
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/services/api/claude.ts:1822-1832`
|
||
- 最终仍通过 `anthropic.beta.messages.create(...)` 发送
|
||
|
||
补充比对:
|
||
|
||
- `src/constants/prompts.ts`
|
||
- `src/context.ts`
|
||
- `src/utils/api.ts`
|
||
- `src/query.ts`
|
||
|
||
与基线仓库对应文件比对时,未看到针对这条链路的“移除性改造”。
|
||
|
||
因此 F1 在 `free-code-main` 中**没有被移除**。
|
||
|
||
### F5. Feedback 上传未移除
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/components/Feedback.tsx:523-550` 仍会在用户触发时:
|
||
|
||
- 刷新 OAuth
|
||
- 取 auth headers
|
||
- POST 到 `https://api.anthropic.com/api/claude_cli_feedback`
|
||
|
||
这个文件与基线对应文件比对无差异。
|
||
|
||
因此 F5 **未移除**。
|
||
|
||
### F6. Transcript Share 上传未移除
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/components/FeedbackSurvey/submitTranscriptShare.ts:37-94` 仍会收集:
|
||
|
||
- `platform`
|
||
- `transcript`
|
||
- `subagentTranscripts`
|
||
- `rawTranscriptJsonl`
|
||
|
||
并 POST 到:
|
||
|
||
- `https://api.anthropic.com/api/claude_code_shared_session_transcripts`
|
||
|
||
这个文件与基线对应文件比对无差异。
|
||
|
||
因此 F6 **未移除**。
|
||
|
||
### F7. Remote Control / Bridge 未移除
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/bridge/bridgeMain.ts:2340-2435` 仍会采集:
|
||
|
||
- `branch`
|
||
- `gitRepoUrl`
|
||
- `machineName = hostname()`
|
||
- `dir`
|
||
|
||
随后:
|
||
|
||
- `/Users/yovinchen/Downloads/free-code-main/src/bridge/bridgeApi.ts:142-178`
|
||
|
||
仍会把这些字段 POST 到:
|
||
|
||
- `/v1/environments/bridge`
|
||
|
||
上传体中明确包含:
|
||
|
||
- `machine_name`
|
||
- `directory`
|
||
- `branch`
|
||
- `git_repo_url`
|
||
|
||
`src/bridge/bridgeApi.ts` 与基线对应文件比对无差异。
|
||
|
||
因此 F7 **未移除**。
|
||
|
||
### F8. Trusted Device 未移除
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/bridge/trustedDevice.ts:142-159` 仍会向:
|
||
|
||
- `${baseUrl}/api/auth/trusted_devices`
|
||
|
||
提交:
|
||
|
||
- `display_name: Claude Code on ${hostname()} · ${process.platform}`
|
||
|
||
这条链路虽然会受 `isEssentialTrafficOnly()` 影响,但代码并未被删除。
|
||
|
||
`src/bridge/trustedDevice.ts` 与基线对应文件比对无差异。
|
||
|
||
因此 F8 **未移除**。
|
||
|
||
### F10. `/insights` ant-only 上传未移除
|
||
|
||
`/Users/yovinchen/Downloads/free-code-main/src/commands/insights.ts:3075-3098` 仍保留:
|
||
|
||
- `process.env.USER_TYPE === 'ant'` 分支
|
||
- 使用 `ff cp` 上传 HTML report 到 S3
|
||
|
||
这条链路不是默认外部版路径,但它在源码里仍然存在。
|
||
|
||
因此 F10 **未移除**。
|
||
|
||
## 与基线仓库的“未改动区域”总结
|
||
|
||
以下文件经对比未看到差异,说明 `free-code-main` 没有在这些链路上做“移除”改造:
|
||
|
||
- `src/constants/prompts.ts`
|
||
- `src/context.ts`
|
||
- `src/utils/api.ts`
|
||
- `src/query.ts`
|
||
- `src/components/Feedback.tsx`
|
||
- `src/components/FeedbackSurvey/submitTranscriptShare.ts`
|
||
- `src/bridge/bridgeApi.ts`
|
||
- `src/bridge/trustedDevice.ts`
|
||
- `src/commands/insights.ts`
|
||
|
||
这也是为什么报告结论是“部分移除”,而不是“整体移除”。
|
||
|
||
## 最终结论
|
||
|
||
如果把 `docs/local-system-info-egress-audit.md` 中的链路拆开看,`free-code-main` 的状态可以总结为:
|
||
|
||
1. **遥测类默认外发**
|
||
- Datadog: 已移除
|
||
- 1P event logging: 已移除
|
||
- OTel: 已移除
|
||
- GrowthBook remote eval: 默认已失活
|
||
|
||
2. **产品主链路或用户触发上传**
|
||
- 模型 system/user context 外发: 未移除
|
||
- Feedback: 未移除
|
||
- Transcript Share: 未移除
|
||
- Remote Control / Bridge: 未移除
|
||
- Trusted Device: 未移除
|
||
- `/insights` ant-only 上传: 未移除
|
||
|
||
因此,`free-code-main` 的真实定位更适合表述为:
|
||
|
||
**它移除了“遥测/观测型外发实现”,但没有移除“产品功能本身依赖的本地信息外发”。**
|
||
|
||
如果后续目标是做“彻底版本地信息外发移除”,还需要继续处理至少这些区域:
|
||
|
||
- `src/constants/prompts.ts`
|
||
- `src/context.ts`
|
||
- `src/utils/api.ts`
|
||
- `src/components/Feedback.tsx`
|
||
- `src/components/FeedbackSurvey/submitTranscriptShare.ts`
|
||
- `src/bridge/*`
|
||
- `src/commands/insights.ts`
|
||
|