AppConfig与AppState调试输出密钥泄漏面 #148
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
【安全报告】AppConfig与AppState调试输出密钥泄漏面-2026-08-07.md
【安全报告】AppConfig 与 AppState 调试输出密钥泄漏面
feat/sound_opt(HEAD69f850ff8)server-rs/全部 crate(1500+ 个.rs文件),只读feat/sound_opt修复;归属 master 独立安全维护1. 结论摘要
api-server的进程级配置对象AppConfig与运行时状态对象AppState都派生了Debug,且没有任何脱敏。二者合计覆盖了本服务几乎全部密钥:JWT 签名密钥、后台管理员密码、内部服务 token、微信支付私钥与 APIv3 密钥、OSS AK/SK、SpacetimeDB token 与 bootstrap secret,以及全部 LLM / 图像 / 音频 provider 的 API key。任何一处
format!("{state:?}")或tracing::debug!(?state, ...)都会把这些值一次性打进日志。穷举搜索确认:当前生产代码中不存在这样的调用点,因此这是潜在泄漏而非已发生的泄漏。但当前的安全性来自"碰巧没人写过",而不是"写了也不会漏"——没有类型系统防线,没有工程规约,没有回归测试。触发成本是一行代码。
本报告在既有记录的基础上新增三项发现,其中第一项扩大了泄漏面:
SpacetimeClient的手写Debug实现本身就在泄漏 token,构成一条独立于AppConfig的第二路径;只修AppConfig修不掉它。#[instrument]在全仓从未被使用,这一类常见的隐式泄漏机制在本仓库不存在。2. 泄漏面:两条独立路径
路径一:
AppConfig直接派生 Debug全仓搜索
impl (std::fmt::)?Debug for (AppConfig|AppState|AppStateInner)— 零命中。Debug全部来自 derive,没有任何字段被隐去。AppState与AppStateInner逐层放大这条路径:即
?state会递归下钻到完整AppConfig。路径二:
SpacetimeClient的手写 Debug(既有记录未覆盖)这条路径独立于路径一,且更隐蔽——它出自一个特地手写、但没有做脱敏的
Debug实现:而被塞入的类型是普通 derive:
token由AppConfig.spacetime_token(config.rs:167)灌入(state.rs:1747、state.rs:1762),而AppStateInner同时持有spacetime_client字段。结果:一次
?state会让 SpacetimeDB token 从两条独立路径各泄漏一次。 将来若只给AppConfig打脱敏补丁,这条路径依然畅通。注:
spacetime-client/src/lib.rs:1243-1254有一份几乎相同的实现,但该 crate 的Cargo.toml指定path = "src/active.rs",lib.rs不在编译单元内,属孤立文件。3. 敏感字段清单
AppConfig(config.rs:33-216)中属密钥/凭证/签名材料的字段(按子系统归类,行号为当前工作区):jwt_secret(91)admin_username(86)、admin_password(87)internal_api_secret(89)、bgfilter_internal_token(41)、editor_bgfilter_token(74)wechat_app_secret(120)、wechat_mini_program_app_secret(122)、wechat_pay_private_key_pem(140)、wechat_pay_private_key_path(141)、wechat_pay_api_v3_key(145)、wechat_mini_program_virtual_payment_app_key(150)、..._sandbox_app_key(151)、wechat_mini_program_message_token(154)、..._encoding_aes_key(155)oss_access_key_id(159)、oss_access_key_secret(160)spacetime_token(167)、spacetime_runtime_service_bootstrap_secret(168)llm_api_key(174)、vector_engine_api_key(190)、elevenlabs_api_key(194)aliyun_matting_access_key_id(78)、..._secret(79)、sms_access_key_id(102)、sms_access_key_secret(103)editor_bgfilter_base_url(73,默认值为硬编码内网 IP)以上字段均无
#[serde(skip)]、无自定义Debug、无包装类型。elevenlabs_api_key(194)由提交c3b3b47c9(SFX V2 T3,2026-08-07)引入。它进入的是一个已有二十余个同类字段处于同样状态的既有面,不是根因。4. 为什么目前是"潜在"而非"已发生"
五道门当前同时关着,逐条为穷举核查结果:
{config:?}/{state:?}/dbg!/println!)Debug捕获(?config/?state/config = ?)#[instrument](默认 Debug 捕获全部函数参数)PuzzleApiState(state.rs:145-226)被#[cfg(any())]永久禁用;OpenAiImageSettings内嵌Option<AppState>但手写 Debug 只打.is_some()Serialize/with_details/ 环境变量转储 / 启动日志)AppConfig无Serialize;/healthz、/readiness、/api/speech/volcengine/config、/api/runtime/frontend-config、/admin/api/debug/http逐一核过,返回的都是独立精简 DTO;启动日志只打bind_address/worker_threads/otel_enabled等开关摘要此外还有一道配置层的门:默认日志级别是
info而非debug。api-server 默认
"info,tower_http=info"(config.rs:341),可被GENARRATIVE_API_LOG/RUST_LOG覆盖。因此即便存在debug!(?state),默认配置下事件会被EnvFilter丢弃,也不会进 OTel 管道(bridge 同样是LevelFilter::INFO)。这道门的边界必须说清楚:它是运行时配置而非代码约束。本次审计只覆盖仓库代码,未核查生产环境实际的
RUST_LOG/GENARRATIVE_API_LOG取值。若要据此下"线上无泄漏"的结论,需要单独确认部署侧配置。shared-logging本身没有任何字段过滤或脱敏逻辑,只做级别过滤。5. 现有防线的实际覆盖率
5.1 脱敏覆盖 2 / 14
全仓 provider / 配置类型的
Debug现状:ElevenLabsAudioSettingsplatform-audio/src/elevenlabs.rs:28api_key→[redacted]OpenAiImageSettingsapi-server/src/openai_image_generation.rs:50api_key→<redacted>,内嵌Option<AppState>只打.is_some()MattingClientplatform-matting/src/lib.rs:286finish_non_exhaustive()间接隐去;其MattingConfig自身仍是明文 deriveVectorEngineAudioSettingsplatform-audio/src/types.rs:83VectorEngineImageSettingsplatform-image/src/vector_engine/types.rs:3LlmConfigplatform-llm/src/lib.rs:63OssConfigplatform-oss/src/lib.rs:71WechatConfigplatform-wechat/src/subscribe_message.rs:26WechatPayConfigplatform-wechat/src/pay.rs:97RealWechatPayClientplatform-wechat/src/pay.rs:121Hyper3dSettingsplatform-hyper3d/src/types.rs:1VolcengineSpeechConfigplatform-speech/src/lib.rs:67MattingConfigplatform-matting/src/lib.rs:48SpacetimeClientConfigspacetime-client/src/active.rs:109AppConfigapi-server/src/config.rs:315.2 测试覆盖
仓库有成熟的"哨兵值"测试纪律(构造独特假密钥,断言输出不含它),共 12 处。但它们系统性地只覆盖**"HTTP 响应体 / error 字符串会不会回显上游明文"**这一类出口——微信支付回调、Prompt Assist 上游透传等链路覆盖密集。
针对 Debug 输出的哨兵测试只有 3 处,且全部只测
ElevenLabsAudioSettings:platform-audio/src/elevenlabs.rs:318settings_debug_redacts_the_api_keyplatform-audio/tests/elevenlabs.rs:305-306api-server/src/vector_engine_audio_generation/settings.rs:115没有任何一处覆盖
AppConfig/AppState/AppStateInner。5.3 无类型防线、无规约
secrecy/zeroize/SecretString/Redacted<T>在全部 39 个 crate 的Cargo.toml与源码中零命中(Cargo.lock里的zeroize是ring/rustls的间接依赖,无直接使用)。因此,下一个写
debug!(?state, "...")的人不会被任何机制拦下。6. 风险性质
tracing::debug!(?state, "...")或format!("{config:?}")定级 P2 合理。但需要注意它的性质不是"影响有限",而是"目前未被触发"——一旦触发,后果是 P0 级。这类风险的正确处置是降低触发概率(脱敏 + 测试),而不是持续依赖代码审查发现
?state。7. 修复建议(分级,供 master 侧排期)
第一档(低成本,覆盖主要面)
AppConfig手写Debug:敏感字段打<redacted>,非敏感字段照常输出。可直接照抄OpenAiImageSettings(openai_image_generation.rs:50-76)的写法——它已经示范了"内嵌对象只打.is_some()、不递归下钻"。SpacetimeClientConfig(或改SpacetimeClient的手写 Debug,不再透传整个config)。这一步不能省,否则 token 仍从路径二泄漏。AppConfig/AppState,断言format!("{:?}")不含任一哨兵。模板见platform-audio/src/elevenlabs.rs:318。第二档(补齐覆盖)
对 §5.1 表中其余 11 个 ❌ 类型逐个补脱敏 Debug,每个配一条哨兵测试。
第三档(根治,需独立排期)
引入 secret wrapper 类型(如
secrecy::SecretString),让"密钥字段不能被 Debug 打印"成为类型系统保证而非作者自觉。代价是要动十几个 crate 的字段类型与取值点,不适合与前两档合并。配套:在 AGENTS.md 或 team-conventions 增补一条运行时日志规约("禁止对配置/状态对象做 Debug 格式化"),使其成为可被审查引用的显式条款。
8. 审计方法与可信度边界
已穷举验证(可作为否定性结论的依据):
impl Debug for (AppConfig|AppState|AppStateInner)全仓搜索{config:?}/{state:?}/{:?}搭配 config/state 变量 /dbg!/println!/eprintln!?前缀 Debug 捕获与#[instrument]panic/expect/assert!自定义消息Debug/Display/thiserror属性impl Serialize for AppConfig、env::vars()转储secrecy/zeroize/ secret wrapper 依赖与用法impl ... Debug for全仓手写实现(7 处,逐一判定是否脱敏)已直接读取核对:
config.rs字段区间、state.rs的AppState/AppStateInner定义、spacetime-client/src/active.rs的SpacetimeClientConfig与SpacetimeClient::fmt、shared-logging/src/lib.rs全文、各 provider settings 的 derive 行。未覆盖,若要据此决策需另行确认:
RUST_LOG/GENARRATIVE_API_LOG的真实取值。ring::signature::RsaKeyPair的 Debug 行为。RealWechatPayClient持有该类型,其 Debug 是否泄漏私钥材料取决于ring自身实现,未在本仓库范围内验证。server-rs的其它进程(apps/下的 shell、miniprogram 等)不在本次范围内。9. 与既有记录的关系
分支上已有
docs/project-memory/todos/【已知问题】AppConfig与AppState调试输出敏感配置泄漏-Master遗留-2026-08-07.md(提交b64c3c05e,18 行)。本报告逐条核查后确认该记录的全部陈述成立,无矛盾。本报告相对该记录的增量:§2 路径二(
SpacetimeClient手写 Debug 的独立泄漏路径)、§5.1 脱敏覆盖率清单、§5.2 测试覆盖缺口、§4 第 3 行(#[instrument]未启用)、§8 可信度边界。其中 §2 路径二会影响修复方案的正确性——按既有记录"为
AppConfig、AppState统一实现脱敏 Debug"的表述施工,若不同时处理SpacetimeClientConfig,token 泄漏不会被修掉。kdletters referenced this issue2026-08-07 18:15:40 +08:00