# `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 · ` | | 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()` 处理成 `` 而调用点仍保留: - `/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`