免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

opencodex 多账户认证隐私加固实战:日志脱敏、非 PII 账户标签与邮箱掩码完整方案

opencodex 多账户认证隐私加固实战:日志脱敏、非 PII 账户标签与邮箱掩码完整方案 opencodex 多账户认证隐私加固实战日志脱敏、非 PII 账户标签与邮箱掩码完整方案【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址: https://gitcode.com/gh_mirrors/ope/opencodex导读本文基于 opencodex 仓库devlog/_fin/260624_codex-multi-auth-security-implementation/60_phase60-privacy-labels-docs.md展开系统讲解多账户 Codex 认证场景下的隐私收尾工程请求日志如何用稳定的非 PII 账户标签phex6替代基于池序号的chatgpt-1/chatgpt-2标签、调试日志与 token 刷新错误如何做到保留运维语义、不泄露敏感内容、/api/oauth/status与/api/codex-auth/login-status如何返回掩码邮箱以及对应的测试、浏览器冒烟与隐私扫描验收流程。读完本文你将掌握一套可复用的日志 API 响应 文档三级隐私脱敏设计方法并能在当前仓库中定位到每一处落地实现与测试证据。背景多账户认证遗留的隐私泄漏点Phase 60 的目标是关闭devlog/280_codex-multi-auth-security-patch-plan/00_patch_plan.md中的 Patch 6核心对象是可部署的多账户 Codex 认证诊断链路。在此之前隐私泄漏分散在四个层面请求日志依赖池序号打标签formatCodexProviderForLog曾按codexAccounts数组中过滤isMain后的下标生成chatgpt-1、chatgpt-2这类标签。账户一旦重排同一账户的日志标签就发生变化且序号与位置耦合无法稳定关联诊断。持久化日志携带本地别名与 token 刷新细节token 刷新失败抛出的错误消息里含有本地账户别名、上游error_description甚至 token/account id 文本。帧丢弃调试日志打印 payload 预览OCX_DEBUG_FRAMES1时会输出帧内容前 200 字符可能包含 bearer token、真实邮箱等敏感文本。API 响应存在隐私回退/api/oauth/status直接返回原始邮箱Phase 30 遗留的 deferral/api/codex-auth/login-status的临时流转状态可能携带 OAuth 完成时未掩码的邮箱。本阶段遵循的外部依据是 OWASP Logging Cheat Sheet日志应剔除或变换访问令牌、密钥、敏感个人数据非必要不记录文件路径/邮箱与 NIST Privacy Framework隐私风险应作为产品设计的一部分管理而非发布后补救。一、非 PII 账户日志标签从序号到稳定匿名标签1.1 标签的三种家族与格式约束从源码 src/codex/account-label.ts 可以看到账户日志标签实际被设计为三类家族前缀形态含义main字面量Codex 主账户main-pool 参与轮换时的统一标签phex6p 6 位十六进制Codex 池账户本阶段的CODEX_ACCOUNT_LOG_LABEL_REohex6o 6 位十六进制非 Codex 的 OAuth 提供商账户xai、cursor 等khex32k 32 位十六进制请求自有的 API-key 选择按 provider reference 摘要生成统一的正则ACCOUNT_LOG_LABEL_RE /^(?:main|[po][a-f0-9]{6}|k[a-f0-9]{32})$/约束全部标签形态。源码注释明确说明标签中绝不包含邮箱、原始 key/reference 或原始 provider 账户 id——这是隐私需求而非格式偏好因为这些标签会被写入 usage 日志并经管理 API 对外提供。1.2 生成、回退与使用三件套Phase 60 计划新增的模块计划中的src/codex-account-label.ts在仓库中落地为 src/codex/account-label.ts包含四个核心函数随机标签生成createCodexAccountLogLabel(existingLabels)用randomBytes(3).toString(hex)生成p 6 位随机十六进制最多尝试 16 次避开已有标签兜底使用 6 字节随机数的前 6 位export function createCodexAccountLogLabel(existingLabels: Iterablestring | undefined | null []): string { const used new Set([...existingLabels].filter((value): value is string !!value)); for (let i 0; i 16; i) { const label p${randomBytes(3).toString(hex)}; if (!used.has(label)) return label; } return p${randomBytes(6).toString(hex).slice(0, 6)}; }确定性回退标签fallbackCodexAccountLogLabel(accountId)对 account id 做 sha256 摘要并取前 6 位。关键点在于旧账户没有logLabel字段时日志格式化路径只读不写绝不因打日志而回写配置而是即时算出稳定的伪匿名标签export function fallbackCodexAccountLogLabel(accountId: string): string { return p${createHash(sha256).update(accountId).digest(hex).slice(0, 6)}; }统一取值入口codexAccountLogLabel(account)优先采用合法存量标签否则走 sha256 回退export function codexAccountLogLabel(account: CodexAccount): string { return CODEX_ACCOUNT_LOG_LABEL_RE.test(account.logLabel ?? ) ? account.logLabel! : fallbackCodexAccountLogLabel(account.id); }入库时的标签注入withCodexAccountLogLabel(account, existingAccounts)仅在账户对象没有合法标签时生成新标签已有合法标签则原样保留——幂等避免重复生成破坏存量诊断关联。设计取舍源码注释原文hex6 空间约 1670 万个值两个账户理论上可能碰撞并合并为同一行报告。这在运维规模下是可接受的报告不精确不是正确性或隐私失败也是保持既有p格式字节级兼容的代价。1.3 路由日志格式化摆脱池序号计划中的改动落地于 src/codex/routing.ts。最终实现还额外处理了主账户主 Codex 登录以main-pool身份参与轮换MAIN_CODEX_ACCOUNT_ID与null accountId的main直通实为同一物理账户统一按基础 provider 名记日志避免 usage/token 统计被拆成chatgptchatgpt-main两行export function formatCodexProviderForLog(providerName: string, accountId: string | null, config: OcxConfig): string { if (!accountId) return providerName; if (accountId MAIN_CODEX_ACCOUNT_ID) return providerName; const account (config.codexAccounts ?? []) .find(candidate isSelectableCodexPoolAccount(candidate) candidate.id accountId); return account ? ${providerName}-${codexAccountLogLabel(account)} : providerName; }由此得到的日志形态是稳定的chatgpt-pabc123风格账户重排不会改变已打标签账户的日志标识无标签账户得到稳定哈希标签且绝不包含原始 account id未知 account id 回退为基础 provider 名。该函数被audio-upstream、context-history、images等多个 server 侧模块用于统一 provider 日志投影。1.4 类型层与账户生命周期集成CodexAccount类型新增可选字段logLabel?: string见计划中src/types.ts的修改仓库侧对应 src/types/accounts.ts 与 src/codex/auth-api/account-list.ts。两条账户创建路径都通过withCodexAccountLogLabel(..., accounts)注入标签手动导入路径accounts.push(withCodexAccountLogLabel({ id: body.id, email: body.email, plan: body.plan, isMain: false }, accounts))OAuth 登录完成路径accounts.push(withCodexAccountLogLabel({ id: accountId, email, plan, isMain: false }, accounts))。计划明确默认账户 API 的邮箱掩码行为不变logLabel若随对象展开一并返回是可接受的因为它刻意不含 PII仅用于在 UI 与请求日志之间做不泄露身份的关联。二、日志脱敏帧预览、错误消息与密钥2.1 帧丢弃日志只留字节数不留内容计划要求OCX_DEBUG_FRAMES1不再打印任何帧 payload 预览。仓库落地位于 src/lib/debug.tsexport function debugDroppedFrame(adapter: string, payload: string): void { if (!isDebugEnabled()) return; emitDebugLine([ocx:frame-drop] ${adapter}: dropped malformed upstream frame (payload redacted, bytes${payload.length})); }日志保留 adapter 名与字节长度永不输出内容。调用点分布在anthropic、command-code、google等流式适配器中。配套的单测debugDroppedFrame redacts payload content通过 spy 断言日志行包含openai-chat与payload redacted但不包含secret frame body与bearer-tokenexample.test。测试文件见 tests/lib/debug.test.ts。同一模块还提供debugProviderDiagnostic/debugProviderDiagnosticLazy所有结构化诊断 details 都会经过redactSecrets再序列化且任何日志抛错都不影响请求处理diagnostics must never affect request handling。Lazy变体接受 builder 函数而非已构建对象保证调试关闭时不做昂贵的投影计算、构建期异常也被吞掉。2.2 Token 刷新错误分类保留、文本丢弃计划要求 token 刷新错误不再包含本地别名、上游error_description、refresh/access token 或 account id。仓库侧落地于 src/codex/account-store.ts维护TokenRefreshError类型携带reason分类并有isTokenRefreshError/ 提取 reason 的辅助函数上游错误体只被解析用于分类如识别invalid_grant分类完成后丢弃文本抛出消息统一为脱敏形态throw new TokenRefreshError(reason, Codex token refresh failed (${reason}); reauthenticate the account.);账户缺失路径抛出的消息统一为Codex account credential is unavailable; reauthenticate the account.不携带请求的本地别名。即运维仍能区分expired/revoked等失败类别但无法从错误消息里反推账户身份或凭据。2.3 邮箱掩码原语与 fail-closed 开关计划中maskEmail从src/codex-auth-api.ts抽到叶子工具模块仓库落地为 src/lib/privacy.tsexport function maskEmail(value: string | null | undefined): string | null { if (!value) return null; const at value.indexOf(); if (at 0) return value; const local value.slice(0, at); const domain value.slice(at 1); if (!domain) return value; if (local.length 1) return *${domain}; if (local.length 2) return ${local[0]}*${domain}; return ${local[0]}***${local[local.length - 1]}${domain}; }掩码规则本地名 1 字符 →*domain2 字符 →a*domain更长 → 保留首尾字符中间三颗星。maskEmail由 src/codex/auth-api.ts 对外 re-export供既有调用方继续使用。此外该模块还提供三个关键的 fail-closed 设计emailMaskingEnabled(config)config?.privacy?.maskEmails ! false。任何歧义缺失配置、缺失privacy块、缺失或畸形maskEmails一律掩码只有字面量布尔false才允许不掩码。手改的false字符串或拼写错误都不会静默泄露地址。配置 schema 见 src/config/schema/config-schema.tsprivacy: z.object({ maskEmails: z.boolean().optional() }).strict().optional().catch(undefined)。projectEmail(value, mask)单一脱敏决策点统一空值归一化为null避免是否存在邮箱的判断因掩码开关不同而行为漂移。maskAccountId(value)对账户 id 做非识别化投影短 id≤4 字符整体替换为account-…其余保留末 4 位。三、API 响应掩码OAuth 状态与登录状态3.1/api/oauth/status掩码邮箱关闭 Phase 30 deferral计划要求 OAuth 状态接口返回掩码邮箱且不改变存储凭据数据。落地于 src/oauth/index.ts核心变更return { loggedIn: !!cred, email: maskEmail(cred?.email) ?? undefined, error: st?.error, done: st?.done ?? false };即只对出站投影做掩码cred底层存储的原始邮箱保持不动。对应测试 tests/oauth/oauth-status-privacy.test.ts 验证种入带完整邮箱的 provider 凭据后getLoginStatus(provider)返回的email已掩码且原始邮箱缺席。3.2/api/codex-auth/login-status掩码临时流转状态计划要求即便内存中的流转状态因 OAuth 完成临时携带未掩码邮箱wire 响应也必须安全。仓库落地于 src/codex/auth-api/login-flow.tsreturn jsonResponse(st ? { ...st, email: projectEmail(st.email, maskFlowEmails) ?? undefined } : { status: expired }); // ... if (st.status pending) return jsonResponse({ ...st, email: projectEmail(st.email, maskFlowEmails) ?? undefined });直接 flow-id 响应与 legacy pending 回退两条路径都走projectEmail。maskFlowEmails在请求边界读取一次emailMaskingEnabled保持 fail-closed 语义。3.3 账户列表 API 保持默认掩码账户列表投影在 src/codex/auth-api/account-list.tsprojectEmail(account.email, maskEmails)maskEmails由emailMaskingEnabled(runtimeConfig)决定主账户条目在无邮箱时归一为Codex App login。CLI 侧ocx status同样遵守操作者自身的privacy.maskEmails决策见 src/cli/index.ts。四、测试与验证矩阵4.1 自动化测试覆盖Phase 60 计划列出的测试在仓库中的落点与断言账户标签测试tests/codex/account-label.test.ts 方向计划tests/codex-account-label.test.tscreateCodexAccountLogLabel()输出匹配CODEX_ACCOUNT_LOG_LABEL_RE且避开既有标签withCodexAccountLogLabel()保留合法存量标签、为新账户生成标签codexAccountLogLabel()回退确定性且不含原始 account id。会话亲和/路由日志标签tests/codex/session-affinity.test.ts 方向存量logLabel产出稳定chatgpt-pabc123风格账户重排不改变标签无标签账户产出稳定哈希标签未知 account id 返回基础 provider。Codex Auth APItests/codex/auth-api.test.ts 方向手动建号存储logLabelGET /api/codex-auth/accounts仍返回掩码邮箱GET /api/codex-auth/login-status掩码直接 flow-id 与 legacy pending 两条路径的流转邮箱。手动导入默认禁用需OPENCODEX_ENABLE_UNVERIFIED_CODEX_IMPORT1配合既有手动导入 harness 才能跑该分支。OAuth 状态隐私tests/oauth/oauth-status-privacy.test.ts掩码且原始邮箱缺席若 store helper 不适合直接单测计划允许下沉到tests/server-auth.test.ts通过/api/oauth/status端到端覆盖。账户 store 脱敏tests/codex/account-store.test.ts 方向缺失账户拒绝消息不含本地别名mock 上游错误的过期刷新以TokenRefreshError拒绝错误消息不含别名、上游描述、access/refresh token、account id。帧日志脱敏tests/lib/debug.test.ts见上文 2.1OCX_DEBUG_FRAMES1下仅输出 adapter 与字节数。聚焦验证命令继承自计划bun test tests/codex/account-label.test.ts tests/codex/session-affinity.test.ts tests/codex/auth-api.test.ts tests/oauth/oauth-status-privacy.test.ts tests/codex/account-store.test.ts tests/lib/debug.test.ts tests/server-auth.test.ts4.2 全量本地门禁bun run typecheck bun test tests cd gui bun run build git diff --check4.3 GUI 浏览器冒烟Codex Auth 页面掩码验证计划给出的流程以一次性OPENCODEX_HOME从当前源码启动代理通过 config/API 种入raw-gui-emailexample.test风格的 fixture Codex 账户用cli-jaw browser打开 Codex Auth 页面断言 DOM 在账户列表、active/switch 确认流可行处、toast 路径可行处中包含掩码值且不包含原始 fixture 邮箱。若环境无法运行真实浏览器冒烟则记录确切的 precondition 失败并以代码级账户 API 掩码测试作为证据下限。4.4 隐私扫描计划给出两条rg扫描命令用于防止新提交引入敏感字面量rg -n Bearer [A-Za-z0-9._-]|access[_-]?token\s*[:]|refresh[_-]?token\s*[:]|[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}|/Users/[A-Za-z0-9._-] src tests devlog/280_codex-multi-auth-security-patch-plan devlog/_plan/260624_codex-multi-auth-security-implementation rg -n chatgpt-[0-9]|frame body|payload preview|Token refresh failed for|Codex account not found: src tests devlog/280_codex-multi-auth-security-patch-plan devlog/_plan/260624_codex-multi-auth-security-implementation扫描口径tests/**允许example.test这类测试域邮箱devlog/**允许 jawdev 报告所需的绝对项目路径新增/变更的 tracked 文件中不得出现真实个人邮箱、bearer/access/refresh token 字面量或本地用户名 home 路径。五、文档治理历史文档标注与最终验证清单5.1 历史270文档的 supersession 标注对devlog/270_codex-multi-account-auth/下 9 个文件如20_phase2-passthrough-override.md、30_phase3-management-api.md、50_phase5-tests-and-hardening.md、70_phase7-quota-capture-autoswitch.md、80_phase8-e2e-hardening.md、120_phase12-production-verification.md、130_oauth-token-collision-fix.md、150_post-implementation-verification-inventory.md、160_post-implementation-verification-results.md在文件顶部添加统一 banner不重写历史正文保留作为 provenance Superseded security note (2026-06-25): This document predates the 280 security patch plan and Phase 10-60 hardening. Treat release-readiness, full-email UI, ordinal request-log labels, unauthenticated management API, fail-open fallback, and earlier account-boundary claims here as historical only. Current merge/deploy evidence is tracked under devlog/280_codex-multi-auth-security-patch-plan/ and devlog/_plan/260624_codex-multi-auth-security-implementation/.这一机制解决的是文档漂移问题早期270系列宣称的 release-readiness、全邮箱 UI、序号日志标签、未认证管理 API、fail-open 回退等结论在 280 补丁计划与 Phase 10-60 加固后已不再代表当前状态必须显式标记为历史。5.2 最终验证清单新增devlog/280_codex-multi-auth-security-patch-plan/10_final-verification-manifest.md记录package 分支/HEAD commit、OS 与 Bun 版本、Patch 1-6 的实现 commit 列表、文档证据路径、本地验证命令与测试计数、独立验证人及摘要、已完成的 runtime/browser/API 探针以及有意推迟的用例无 live 上游 token replay/revocation 测试无超出 file-lock/CAS 单元覆盖的多进程压力测试无非 loopback 的生产部署验证除非用户明确要求 push否则不进行 push/CI 运行。清单本身不得包含个人账户邮箱、本地用户名、原始 home 路径、token 或截图——验证文档自身也遵守它要验证的隐私纪律。六、变更全景与实施纪律计划的 File Change MapMermaid 依赖图清晰呈现了模块边界privacy.ts作为叶子工具被codex-auth-api与oauth/index.ts共同依赖codex-account-label.ts同时被codex-routing、codex-auth-api、types引用codex-account-store与debug的脱敏改动影响 durable service logs各改动点均有对应测试文件承接。实施纪律PABCD Notes要点分类为C4 security/privacy hardening计划审计必需构建验证需独立只读员工复核后方可进入 C 阶段Phase 60 以单个原子 commit提交不 push / 不 reset / 不 clean。结语隐私加固的通用模式回看 Phase 60可以提炼出一套可复用的隐私加固模式适用于任何把诊断可观测性与敏感数据保护同时作为目标的多账户代理服务标识符层为日志/报告引入刻意不含 PII 的匿名标签族随机生成 哈希回退 幂等注入把身份边界从标签设计中剥离日志层帧内容、错误描述、token 字面量全部在输出边界脱敏只保留分类、字节数、adapter 等运维必需元数据分类解析与文本丢弃分离API 层所有出站投影统一走单一掩码原语配置开关 fail-closed只有字面量false才解锁存储数据保持原样测试层每个脱敏点配 spy 断言包含脱敏标记、不包含原文并有独立隐私扫描命令做回归防线文档层历史结论用 supersession banner 显式标记最终验证清单记录证据与明确推迟项避免未验证即宣称就绪。这套设计在当前仓库中均有源码与测试可追溯掩码原语见 src/lib/privacy.ts标签体系见 src/codex/account-label.ts帧脱敏见 src/lib/debug.ts错误脱敏见 src/codex/account-store.ts登录状态掩码见 src/codex/auth-api/login-flow.ts测试证据见 tests/oauth/oauth-status-privacy.test.ts 与 tests/lib/debug.test.ts。【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址: https://gitcode.com/gh_mirrors/ope/opencodex创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表