diff --git a/qy_lty/.planning/REQUIREMENTS.md b/qy_lty/.planning/REQUIREMENTS.md index 3a7d8ea..df8f2d4 100644 --- a/qy_lty/.planning/REQUIREMENTS.md +++ b/qy_lty/.planning/REQUIREMENTS.md @@ -100,7 +100,7 @@ - [x] **CRED-03** 管理端 GET `/api/v1/admin/credential-slot/`:admin token 鉴权(`admin_token:{token}` Redis key 体系);返回 `{ app_id, access_token: , updated_at }`,Access Token 仅返回末 4 位脱敏掩码 - [x] **CRED-04** 管理端 PUT `/api/v1/admin/credential-slot/`:admin token 鉴权;接受 `{ app_id, access_token }` 全字段覆写更新;空记录场景自动 `get_or_create`;变更写入 `updated_at` - [x] **CRED-05** 客户端 GET `/api/credential-slot/`:user token 鉴权(`token:{token}` Redis key 体系,复用 `RedisTokenAuthentication`);**明文**返回 `{ app_id, access_token, updated_at }`(手机端 / 设备端实际调用第三方服务需要) -- [ ] **CRED-06** Access Token 日志过滤:阿里云日志格式化器 / 自定义日志过滤器中识别 `access_token` 字段并脱敏,覆盖 PUT 请求体、admin GET 响应体两条最易泄露路径 +- [x] **CRED-06** Access Token 日志过滤:阿里云日志格式化器 / 自定义日志过滤器中识别 `access_token` 字段并脱敏,覆盖 PUT 请求体、admin GET 响应体两条最易泄露路径 ✓ Plan 03-02 完成(2026-05-08,commits 891a5ea / 35eb110 / 7a9e511 / db4d5cf;AccessTokenMaskFilter 4 正则覆盖 JSON / Pyrepr / URL query / 等号或冒号兜底;端到端 9 truth × 32 项断言全 PASS) ### 候选优先级(已转移自 brownfield 文档化阶段,本期不消化) @@ -142,11 +142,11 @@ | CRED-02 Django Admin 注册(脱敏 + 隐藏新增按钮) | Phase 1 凭据槽位数据层 | Done(Plan 01-02,2026-05-07) | | CRED-03 管理端 GET(admin token,脱敏返回) | Phase 2 管理端读写接口 | Done(Plan 02-01 + 02-02,2026-05-07) | | CRED-04 管理端 PUT(admin token,全字段覆写 + get_or_create) | Phase 2 管理端读写接口 | Done(Plan 02-01 + 02-02,2026-05-07) | -| CRED-05 客户端 GET(user token,明文返回) | Phase 3 客户端读取与日志脱敏 | Pending | -| CRED-06 Access Token 阿里云日志过滤 | Phase 3 客户端读取与日志脱敏 | Pending | +| CRED-05 客户端 GET(user token,明文返回) | Phase 3 客户端读取与日志脱敏 | Done(Plan 03-01,2026-05-08) | +| CRED-06 Access Token 阿里云日志过滤 | Phase 3 客户端读取与日志脱敏 | Done(Plan 03-02,2026-05-08) | -**覆盖率**:6/6 Active 需求映射到 Phase ✓(无孤儿,无重复) +**覆盖率**:6/6 Active 需求映射到 Phase ✓(无孤儿,无重复);**6/6 全部 Done — Milestone v1.0 完结** --- -*Last updated: 2026-05-07 — Phase 2 完成(Plan 02-01 + Plan 02-02 全部交付,CRED-03 / CRED-04 标记 Done);下一步启动 Phase 3 规划(CRED-05 客户端 GET 明文 + CRED-06 阿里云日志脱敏)* +*Last updated: 2026-05-08 — Phase 3 完成(Plan 03-01 + Plan 03-02 全部交付,CRED-05 / CRED-06 标记 Done);Milestone v1.0「通用凭据槽位(APP ID + Access Token)」CRED-01 至 CRED-06 全部交付;下一周期 milestone 候选评估见上方「候选优先级」段* diff --git a/qy_lty/.planning/ROADMAP.md b/qy_lty/.planning/ROADMAP.md index 3213fa0..7fe0782 100644 --- a/qy_lty/.planning/ROADMAP.md +++ b/qy_lty/.planning/ROADMAP.md @@ -6,7 +6,7 @@ ## Milestones -- 🚧 **v1.0 通用凭据槽位** — Phase 1-3(启动 2026-05-07) +- ✅ **v1.0 通用凭据槽位** — Phase 1-3 全部交付(2026-05-07 启动 / 2026-05-08 完结,CRED-01 至 CRED-06 全 Done) ## Phases @@ -18,7 +18,7 @@ - [x] **Phase 1: 凭据槽位数据层** — 落地 `CredentialSlot` 单例模型 + 迁移 + Django Admin 注册(脱敏 + 隐藏新增按钮)✓ 2026-05-07 完成(Plan 01-01 + 01-02) - [x] **Phase 2: 管理端读写接口** — 在 `/api/v1/admin/` 暴露凭据槽位 GET(脱敏)/ PUT(覆写)端点,admin token 鉴权 ✓ 2026-05-07 完成(Plan 02-01 + 02-02) -- [ ] **Phase 3: 客户端读取与日志脱敏** — 在 `/api/credential-slot/` 暴露明文读取端点(user token 鉴权),并在阿里云日志链路过滤 `access_token` +- [x] **Phase 3: 客户端读取与日志脱敏** — 在 `/api/credential-slot/` 暴露明文读取端点(user token 鉴权),并在阿里云日志链路过滤 `access_token` ✓ 2026-05-08 完成(Plan 03-01 + 03-02) ## Phase Details @@ -58,8 +58,8 @@ 3. 在生产日志(阿里云日志服务)中检索 Phase 2 / Phase 3 的请求轨迹:`PUT /api/v1/admin/credential-slot/` 请求体里的 `access_token` 字段被脱敏;管理端 `GET` 响应体里的 `access_token` 同样脱敏;客户端明文 GET 端点的响应体不写入日志(或同样脱敏),无任何位置暴露完整 Access Token 明文 4. 端到端验证:管理后台用 PUT 写入一组凭据 → 手机端调用客户端 GET 拿到的 `app_id` / `access_token` 与管理端写入的一致(往返一致性成立) **Plans:** 2 plans - - [x] 03-01-PLAN.md — 客户端凭据槽位 GET 接口(CRED-05:CredentialSlotClientView 明文返回 + /api/credential-slot/ 路由注册) - - [ ] 03-02-PLAN.md — 阿里云日志 access_token 脱敏(CRED-06:AccessTokenMaskFilter + LOGGING 配置 + 修改记录) + - [x] 03-01-PLAN.md — 客户端凭据槽位 GET 接口(CRED-05:CredentialSlotClientView 明文返回 + /api/credential-slot/ 路由注册)✓ 2026-05-08(commits 5269a08 / 50dcf1c / a58980f) + - [x] 03-02-PLAN.md — 阿里云日志 access_token 脱敏(CRED-06:AccessTokenMaskFilter + LOGGING 配置 + 修改记录)✓ 2026-05-08(commits 891a5ea / 35eb110 / 7a9e511 / db4d5cf;端到端 9 truth × 32 项断言全 PASS) ## Progress @@ -70,8 +70,11 @@ Phase 按数值顺序执行:1 → 2 → 3(如出现紧急插入,记为 1.1 |-------|----------------|--------|-----------| | 1. 凭据槽位数据层 | 2/2 | ✓ Complete | 2026-05-07 | | 2. 管理端读写接口 | 2/2 | ✓ Complete | 2026-05-07 | -| 3. 客户端读取与日志脱敏 | 0/2 | Not started | - | +| 3. 客户端读取与日志脱敏 | 2/2 | ✓ Complete | 2026-05-08 | + +**Milestone v1.0 总进度:6/6 plans (100%) — 完结 ✓** --- *生成时间:2026-05-07,Milestone v1.0「通用凭据槽位(APP ID + Access Token)」启动* +*最后更新:2026-05-08,Milestone v1.0 完结(CRED-01 至 CRED-06 全部交付)* diff --git a/qy_lty/.planning/STATE.md b/qy_lty/.planning/STATE.md index 00c0cd8..2a922eb 100644 --- a/qy_lty/.planning/STATE.md +++ b/qy_lty/.planning/STATE.md @@ -2,21 +2,21 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: 通用凭据槽位 -status: Phase 3 Plan 03-01 完成(CRED-05 客户端 GET 已落地),等待执行 Plan 03-02 -stopped_at: Plan 03-01 完成(CRED-05 客户端 GET 落地,6 truth × 15 断言全 PASS);下一步 Plan 03-02(CRED-06 日志脱敏) -last_updated: "2026-05-08T02:17:57.222Z" +status: Milestone v1.0 完成 — CRED-01 至 CRED-06 全部交付(Phase 1+2+3 全部 Done);下一周期 milestone 候选评估 +stopped_at: Plan 03-02 完成(CRED-06 日志脱敏 filter 落地,9 truth × 32 断言全 PASS);Milestone v1.0 已收尾 +last_updated: "2026-05-08T02:33:19.667Z" last_activity: 2026-05-08 progress: total_phases: 3 - completed_phases: 2 + completed_phases: 3 total_plans: 6 - completed_plans: 5 - percent: 83 + completed_plans: 6 + percent: 100 --- # Project State — QY LTY Backend -**最后更新**: 2026-05-07(Phase 2 完成:Plan 02-01 + 02-02 全部交付,CRED-03 / CRED-04 标记 Done;端到端 8 条 success criteria 全 PASS;两端修改记录互引闭环) +**最后更新**: 2026-05-08(Phase 3 Plan 03-02 完成:AccessTokenMaskFilter 落地,CRED-06 标记 Done;端到端 9 truth × 32 断言全 PASS;Milestone v1.0「通用凭据槽位」CRED-01~06 全部交付) ## 项目引用 @@ -24,26 +24,26 @@ progress: **核心价值**:设备端与手机端通过同一个 user_id 实时互通——`device_{user_id}` 分组语义必须始终成立。 -**当前重点**:Milestone v1.0 通用凭据槽位(APP ID + Access Token)— Phase 1 + Phase 2 全部完成;下一步启动 Phase 3 规划(CRED-05 客户端 GET 明文 + CRED-06 阿里云日志脱敏)。 +**当前重点**:Milestone v1.0 通用凭据槽位(APP ID + Access Token)— **全部完成**。下一周期候选 milestone 评估见 `REQUIREMENTS.md` 候选优先级段。 ## 当前位置 ``` -Phase: 3 of 3(客户端读取与日志脱敏)— In Progress -Plan: 01 of 02(CRED-05 客户端 GET 接口)— Complete ✓ -Status: Plan 03-01 完成,等待执行 Plan 03-02(CRED-06 日志脱敏) +Phase: 3 of 3(客户端读取与日志脱敏)— Complete ✓ +Plan: 2 of 02(CRED-06 阿里云日志脱敏)— Complete ✓ +Status: Milestone v1.0 完结,等待下一周期 milestone 立项 Last activity: 2026-05-08 ``` -Progress: [████████░░] 83%(已完成 plan:5/6 — Phase 1 全部 + Phase 2 全部 + Phase 3 Plan 01) +Progress: [██████████] 100%(已完成 plan:6/6 — Phase 1 全部 + Phase 2 全部 + Phase 3 全部) ## 性能指标 **速度:** -- 已完成 plan 数:5 -- 平均耗时:~398 s(顺序执行模式) -- 总执行时间:1990 s(Plan 01-01: 184 s + Plan 01-02: ~600 s + Plan 02-01: 216 s + Plan 02-02: ~720 s + Plan 03-01: 270 s) +- 已完成 plan 数:6 +- 平均耗时:~414 s(顺序执行模式) +- 总执行时间:2484 s(Plan 01-01: 184 s + Plan 01-02: ~600 s + Plan 02-01: 216 s + Plan 02-02: ~720 s + Plan 03-01: 270 s + Plan 03-02: 494 s) **按 Phase:** @@ -51,12 +51,12 @@ Progress: [████████░░] 83%(已完成 plan:5/6 — Phase |-------|-------|--------|----------| | 1 | 2/2 | 784 s | 392 s | | 2 | 2/2 | 936 s | 468 s | -| 3 | 1/2 | 270 s | 270 s | +| 3 | 2/2 | 764 s | 382 s | **最近趋势:** -- 最近 5 个 plan:01-01(184 s,3 task)/ 01-02(~600 s,4 task + checkpoint 验收)/ 02-01(216 s,3 task / 3 commit / 3 文件)/ 02-02(~720 s,2 task / 2 commit / 1 创建 + 2 修改文件 + 端到端 28 项断言)/ 03-01(270 s,3 task / 2 commit / 2 修改文件 + 1 临时验收脚本未入 git + 端到端 15 项断言) -- 趋势:纯 auto 代码落地 plan 速度稳定 200-270 s;Plan 03-01 是 1:1 复刻 Phase 2 admin view 模板的最简形态(删 _ensure_admin / _build_response_data / def put,仅保留 GET 明文),加上端到端验收,落地速度接近 Plan 02-01;3 处 Windows 环境兼容偏差全部 auto-fixed +- 最近 6 个 plan:01-01(184 s,3 task)/ 01-02(~600 s,4 task + checkpoint 验收)/ 02-01(216 s,3 task / 3 commit / 3 文件)/ 02-02(~720 s,2 task / 2 commit / 1 创建 + 2 修改文件 + 端到端 28 项断言)/ 03-01(270 s,3 task / 2 commit / 2 修改文件 + 1 临时验收脚本未入 git + 端到端 15 项断言)/ 03-02(494 s,4 task / 4 commit / 2 创建 + 2 修改文件 + 端到端 32 项断言 + 2 处 Rule 1 auto-fix bug) +- 趋势:03-02 略慢于 03-01(494 vs 270 s),原因是 plan 内置 2 处 bug(Pattern 4 兜底 regex 吃尾 + tuple args 形态破坏 %s 占位符)需要现场调试 + auto-fix;nett 端到端覆盖更广(32 vs 15 项断言);Milestone v1.0 整体节奏稳定 200-720 s/plan *每完成一个 plan 后更新* @@ -95,6 +95,13 @@ Progress: [████████░░] 83%(已完成 plan:5/6 — Phase - **[Plan 03-01]** 客户端响应 schema 独立命名 _credential_slot_client_data_schema,access_token description 显式标注「明文」,与 admin 端 _credential_slot_data_schema 对照避免混用脱敏掩码语义 - **[Plan 03-01]** 不调用 logger.info / logger.debug 输出 access_token,未引入新泄露源;Plan 03-02 的 AccessTokenMaskFilter 是兜底防御 - **[Plan 03-01]** 验收脚本 `_phase3_01_verify.py` 不入 git,留在仓库根;Plan 03-02 Task 4 末尾统一删除 + 一并写两端 docs/修改记录.md 互引条目 +- **[Plan 03-02]** AccessTokenMaskFilter 挂在 LOGGING.handlers(aliyun + console)而非 loggers 段:挂 logger 仅过滤直接通过该 logger 的 record,挂 handler 才统一覆盖所有 logger → handler 路径 +- **[Plan 03-02]** dictConfig filter 注册用 `()` 工厂语法不用 `class`:dictConfig 标准对 filter 与 handler 语法不互通 +- **[Plan 03-02]** 4 个 regex 不合并成 1 个大 regex:可读性 + group 数差异(JSON/Pyrepr 是 3 group / Query/Fallback 是 2 group),合并会让 _sub 变脆 +- **[Plan 03-02]** filter 仅识别 access_token 字段名前缀锚点,不脱敏裸 token / Authorization / Bearer:那是另一类敏感数据,留 v2.x 候选优先级处理 +- **[Plan 03-02]** Pattern 4 兜底 regex 终止符增加 `&` / `=` 排除:避免 Pattern 3 的输出 `access_token=********1234&u=1` 被 Pattern 4 把 `********1234&u=1` 整段二次 mask 把末 4 位 `1234` 吃成 `&u=1`(auto-fix Rule 1 Bug) +- **[Plan 03-02]** tuple args 形态走 `record.getMessage()` 预拼接后 `args=None` 再脱敏:避免 Formatter `%` 拼接时占位符被 mask 吃掉触发 TypeError(auto-fix Rule 1 Bug) +- **[Plan 03-02]** 不写 qy-lty-admin/docs/修改记录.md 互引:CONTEXT 锁定 + RESEARCH 实证;客户端给 Unity 用,qy-lty-admin 不消费 /api/credential-slot/ ### Pending Todos @@ -122,18 +129,19 @@ Progress: [████████░░] 83%(已完成 plan:5/6 — Phase ## 下一步 ``` -执行 Phase 3 Plan 02:CRED-06 阿里云日志脱敏 + 两端修改记录互引 + 临时验收脚本删除 +Milestone v1.0 已完结。下一步:评估下一周期 milestone 候选(见 REQUIREMENTS.md 候选优先级段);运行 /gsd-complete-milestone 收口(如启用) ``` -Phase 3 Plan 03-01 已完成(commits 5269a08 / 50dcf1c): +Phase 3 Plan 03-02 已完成(commits 891a5ea / 35eb110 / 7a9e511 / db4d5cf): -- Task 1:`CredentialSlotClientView` + `_credential_slot_client_data_schema` 追加到 `aiapp/views.py` 末尾(commit 5269a08) -- Task 2:`/api/credential-slot/` 路由注册到 `qy_lty/urls.py:api_urlpatterns`(commit 50dcf1c) -- Task 3:端到端 6 条 truth × 15 项独立断言全 PASS — Django test client 跑 user/admin token 双 200 + 401 × 2 + Swagger schema + DB 探针态(脚本 `_phase3_01_verify.py` 留在仓库根未入 git) +- Task 1:`common/logging/__init__.py`(空 package marker)+ `common/logging/filters.py`(106 行 AccessTokenMaskFilter,4 正则 + tuple args 处理 + filter() 方法)(commit 891a5ea) +- Task 2:`qy_lty/settings.py:LOGGING` 注册 `filters` 段(dictConfig `()` 工厂语法)+ `handlers.aliyun` / `handlers.console` 各挂 `filters: ['access_token_mask']`,loggers 段 5 条 logger 完全未动(commit 35eb110) +- Task 3:端到端 9 条 truth × 32 项独立断言全 PASS — 覆盖 03-01 的 5 条 client view + filter 4 形态 + 不误伤 Authorization + admin/client roundtrip + 端到端 logger.info 真打印 console 脱敏 + DB 探针态还原(commit 7a9e511,03-VERIFICATION.md 落地) +- Task 4:`docs/修改记录.md` 顶部追加 [2026-05-08] Phase 3 条目(覆盖 5 处文件 + CRED-05/06 + 跨项目联动「无」明示)(commit db4d5cf) -URL `/api/credential-slot/` GET 已端到端验证(明文返回 access_token);DB 探针态保持 `probe_app` / `probe_secret_xxxx`;CRED-05 已在 REQUIREMENTS.md 标记 Done。 +CRED-06 已在 REQUIREMENTS.md 标记 Done;DB 探针态保持 `pk=1 / app_id='probe_app' / access_token='probe_secret_xxxx'`;临时验收脚本(5 个)全部删除。 -下一步执行 Plan 03-02:CRED-06(`common/logging/filters.py:AccessTokenMaskFilter` + `qy_lty/settings.py:LOGGING.filters/handlers` + 两端 docs/修改记录.md 互引 + 删除 `_phase3_01_verify.py`)。 +**Milestone v1.0「通用凭据槽位(APP ID + Access Token)」CRED-01 至 CRED-06 全部交付完成**。下一周期候选:见上方「Deferred Items」段。 ## 工作流配置 @@ -163,10 +171,10 @@ CLAUDE.md 两条强制规则(任何 phase 都必须遵守): ## Session Continuity -Last session: 2026-05-08T02:17:57.220Z -Stopped at: Plan 03-01 完成(CRED-05 客户端 GET 落地,6 truth × 15 断言全 PASS);下一步 Plan 03-02(CRED-06 日志脱敏) +Last session: 2026-05-08T02:30:22Z +Stopped at: Plan 03-02 完成(CRED-06 日志脱敏 filter 落地,9 truth × 32 断言全 PASS);Milestone v1.0 已收尾,等待下一周期 milestone 立项 Resume file: None --- -*由 /gsd-execute-phase 顺序执行器于 2026-05-07 更新(Plan 02-02 完成时点)* +*由 /gsd-execute-phase 顺序执行器于 2026-05-08 更新(Plan 03-02 完成时点;Milestone v1.0 完结)* diff --git a/qy_lty/.planning/phases/03-client-and-log-mask/03-02-SUMMARY.md b/qy_lty/.planning/phases/03-client-and-log-mask/03-02-SUMMARY.md new file mode 100644 index 0000000..f055e04 --- /dev/null +++ b/qy_lty/.planning/phases/03-client-and-log-mask/03-02-SUMMARY.md @@ -0,0 +1,243 @@ +--- +phase: 03-client-and-log-mask +plan: 02 +subsystem: logging +tags: [logging, dictconfig, filter, mask-token, regex, defensive-mitigation, milestone-v1.0-complete] + +# Dependency graph +requires: + - phase: 01-credential-data-layer + provides: common.utils.mask_token / aiapp.models.CredentialSlot + - phase: 02-admin-rest + provides: aiapp.views.CredentialSlotAdminView(端到端 T8 admin PUT 走它)+ DB 探针态 pk=1/probe_app/probe_secret_xxxx + - phase: 03-01 + provides: aiapp.views.CredentialSlotClientView + /api/credential-slot/ 路由(端到端 T1/T2 走它) +provides: + - common.logging 子包 + common.logging.filters.AccessTokenMaskFilter(logging.Filter 子类,4 正则覆盖 JSON / Pyrepr / URL query / 等号或冒号兜底) + - settings.LOGGING.filters.access_token_mask(dictConfig 工厂语法 () 注册) + - settings.LOGGING.handlers.aliyun.filters / handlers.console.filters(filter 挂在 handler 层,统一覆盖所有 logger → handler 路径) + - docs/修改记录.md 顶部 [2026-05-08] Phase 3 条目(覆盖 5 处文件 + CRED-05/06 + 跨项目联动「无」) + - .planning/phases/03-client-and-log-mask/03-VERIFICATION.md(9 truth × 32 项独立断言全 PASS 报告) +affects: + - 任何后续 view / middleware / 业务代码内 logger.info/debug/warning 调用:含 access_token 字段的字符串 / dict args / tuple args 形态会被自动脱敏;不再依赖每个调用点自律 + - Milestone v1.0「通用凭据槽位」:CRED-01 至 CRED-06 全部 Done,本期目标圆满达成 + +# Tech tracking +tech-stack: + added: [] + patterns: + - "logging.Filter 子类挂在 LOGGING.handlers 层(不挂 loggers 层)—— 一次注册全链路覆盖" + - "dictConfig 工厂语法 () 用于 filter(区别于 handler 用 class)" + - "正则 pattern 终止符设计:Pattern 4 兜底 regex 必须排除 & / = 等 URL 分隔符,避免重复 mask Pattern 3 的输出" + - "tuple args 形态先 record.getMessage() 拼成最终字符串再整体脱敏 + args=None:避免 Formatter 阶段 % 格式化时占位符被吃后报 TypeError" + - "防御性兜底而非修复式补丁:当前仓库无既有泄露路径,filter 是为未来代码改动留的安全网" + +key-files: + created: + - common/logging/__init__.py(空文件,0 字节,包 marker) + - common/logging/filters.py(106 行:AccessTokenMaskFilter 类 + 4 个 _PATTERNS 正则 + _NEEDLE 短路 + _sub / _mask_in_text / filter 方法) + - .planning/phases/03-client-and-log-mask/03-VERIFICATION.md(端到端 9 truth × 32 PASS 报告) + - .planning/phases/03-client-and-log-mask/03-02-SUMMARY.md(本文件) + modified: + - qy_lty/settings.py(LOGGING 字典:新增 filters 段(4 行)+ aliyun handler 加 filters: ['access_token_mask'](1 行)+ console handler 加 filters: ['access_token_mask'](1 行)+ 4 行注释;前后行差 +11 行) + - docs/修改记录.md(顶部追加 [2026-05-08] Phase 3 条目,紧邻 [2026-05-07] Phase 2 之上;+22 行) + deleted: + - _phase3_01_verify.py(plan 03-01 创建的临时验收脚本,本 plan Task 3 末尾统一删除) + - _phase3_02_unit_test.py(Task 1 临时单元测试,验完即删) + - _phase3_02_verify.py(Task 3 端到端验收脚本,验完即删 + 输出落 03-VERIFICATION.md) + - _phase3_02_settings_check.py(Task 2 验收 helper,验完即删) + - _phase3_admin_mtime.txt(Task 4 mtime baseline 临时文件,验完即删) + +key-decisions: + - "[Plan 03-02] AccessTokenMaskFilter 挂在 LOGGING.handlers(aliyun + console)而非 loggers 段:挂 logger 仅过滤直接通过该 logger 的 record,挂 handler 才统一覆盖所有 logger → handler 路径(per RESEARCH Pitfall 1)" + - "[Plan 03-02] dictConfig filter 注册用 () 工厂语法不用 class:dictConfig 标准对 filter 与 handler 语法不互通(per RESEARCH Pitfall 5)" + - "[Plan 03-02] 4 个 regex 不合并成 1 个大 regex:可读性 + group 数差异(JSON/Pyrepr 是 3 group / Query/Fallback 是 2 group),合并会让 _sub 变脆" + - "[Plan 03-02] filter 仅识别 access_token 字段名前缀锚点,不脱敏裸 token / Authorization / Bearer:那是另一类敏感数据,留 v2.x 候选优先级处理(per RESEARCH Pitfall 3)" + - "[Plan 03-02] tuple args 形态走 record.getMessage() 预拼接后 args=None 再脱敏:避免 Formatter % 拼接时占位符被 mask 吃掉触发 TypeError(auto-fix Rule 1,详见 Deviations)" + - "[Plan 03-02] Pattern 4 兜底 regex 终止符增加 & / = 排除:避免 Pattern 3 的输出 access_token=********1234&u=1 被 Pattern 4 把 ********1234&u=1 整段二次 mask 把末 4 位 1234 吃成 &u=1(auto-fix Rule 1,详见 Deviations)" + - "[Plan 03-02] 端到端验收脚本走 python manage.py shell -c \"exec(open(...).read())\" 而非 < 重定向:Windows PowerShell 下 < 触发逐行 REPL 执行,缩进块全部 IndentationError(plan 03-01 已踩坑)" + - "[Plan 03-02] 验收脚本 print 用 [PASS] / [FAIL] 而非 ✓ / ✗ 标记:Windows GBK 控制台无法编码 Unicode 字符(plan 03-01 已踩坑)" + - "[Plan 03-02] 不写 qy-lty-admin/docs/修改记录.md 互引:CONTEXT 锁定 + RESEARCH 实证;客户端给 Unity 用(LTY_Project / LTY_App_Project_URP),qy-lty-admin 不消费 /api/credential-slot/(管理端走 Phase 2 落地的 /api/v1/admin/credential-slot/)" + - "[Plan 03-02] 临时验收脚本(_phase3_01_verify.py / _phase3_02_unit_test.py / _phase3_02_verify.py / _phase3_02_settings_check.py / _phase3_admin_mtime.txt)一律不入 git:与 Phase 2 / Plan 03-01 一致,是一次性证据生成器" + +patterns-established: + - "logging.Filter 子类 + dictConfig () 工厂语法 + 挂 handler 不挂 logger 的脱敏 filter 模式(未来其它字段如 Bearer / api_secret 想脱敏时直接复刻)" + - "正则交叉吃尾的兜底 regex 终止符设计法则:兜底 regex 必须排除前置 regex 的边界字符,避免链式调用时把已脱敏的输出再次当 value" + - "tuple args 形态 LogRecord 处理:getMessage() 预拼 + args=None,避开占位符与 mask 输出的 % 格式化冲突" + +requirements-completed: + - CRED-06 + +# Metrics +duration: 8min14s +completed: 2026-05-08 +--- + +# Phase 3 Plan 03-02:阿里云日志 access_token 脱敏 filter Summary + +**AccessTokenMaskFilter 4 正则脱敏 filter 落地(dictConfig () 工厂语法注册到 LOGGING.handlers.aliyun + console),9 truth × 32 项独立断言全 PASS(CRED-05 + CRED-06 整合验收);docs/修改记录.md 顶部追加 Phase 3 条目,跨项目联动「无」明示;Milestone v1.0「通用凭据槽位」CRED-01~06 至此全部 Done** + +## Performance + +- **Duration:** ~8.2 min(含 2 处 Rule 1 auto-fix bug) +- **Started:** 2026-05-08T02:22:08Z +- **Completed:** 2026-05-08T02:30:22Z +- **Tasks:** 4(包 + filter / settings / 端到端验收 / 修改记录条目) +- **Files modified:** 2(qy_lty/settings.py + docs/修改记录.md) +- **Files created:** 4(common/logging/__init__.py + common/logging/filters.py + 03-VERIFICATION.md + 03-02-SUMMARY.md) +- **Files deleted:** 5 临时(_phase3_01_verify.py + _phase3_02_unit_test.py + _phase3_02_verify.py + _phase3_02_settings_check.py + _phase3_admin_mtime.txt) + +## Accomplishments + +- **CRED-06 落地完成**:`AccessTokenMaskFilter(logging.Filter)` 已注册到 `qy_lty.settings.LOGGING.filters` 并由 `handlers.aliyun` / `handlers.console` 引用;任何 view / middleware / 第三方库的 `logger.info(包含 access_token 明文的字符串)` 都会被脱敏成 `mask_token(value)` 输出 +- **9 truth 全 PASS(32 项独立断言)**: + - T1-T5(5 truth × 14 项断言):plan 03-01 客户端 GET 5 项验证(user/admin token 200 + 401 × 2 + swagger schema) + - T6(4 项):filter 4 种正则形态(JSON / Pyrepr / Query / Fallback)伪 LogRecord 处理后含末 4 位 `1234`、不含完整明文 `abcdefgh` + - T7(2 项):filter 不误伤 `Authorization header: bearer_user_token_xxxxxxx` / `Bearer raw_token_zzz` 等非 access_token 字段 + - T8(5 项):端到端 admin PUT roundtrip → admin GET 脱敏 `**********RT99` + client GET 明文 `rt_secret_RT99` + app_id 一致 + - T9(2 项):端到端 `logger.info('access_token=defensive_secret_DEFC')` → console 输出 `*****************DEFC` 脱敏(防御性兜底真实生效) + - T_FINAL(2 项):DB 探针态主动还原 `pk=1 / app_id='probe_app' / access_token='probe_secret_xxxx'` +- **DB 探针态保持稳定**:脚本前后 `pk=1, app_id='probe_app', access_token='probe_secret_xxxx'`,给后续工作(无论是下一周期 milestone 还是 v2.x 评估)留下与 Phase 1/2/3-01 一致的稳定起点 +- **Phase 1 / Phase 2 / Plan 03-01 既有代码 0 改动**:`CredentialSlot` 模型 / Admin 注册 / `CredentialSlotAdminView` / `CredentialSlotClientView` / 既有 5 条 logger 配置完全未动;imports 段未变 +- **Milestone v1.0 完结**:CRED-01(单例 model)+ CRED-02(Admin 注册)+ CRED-03(admin GET 脱敏)+ CRED-04(admin PUT 覆写)+ CRED-05(客户端 GET 明文)+ CRED-06(日志脱敏 filter)全部 Done + +## Task Commits + +每个 task 原子提交(中文 commit message): + +1. **Task 1: 新建 common/logging/ 包 + AccessTokenMaskFilter** — `891a5ea` (feat) — common/logging/__init__.py(0 字节)+ common/logging/filters.py(106 行) +2. **Task 2: settings.py LOGGING 注册 access_token_mask filter** — `35eb110` (feat) — LOGGING 字典 +11 行(filters 段 + 2 个 handler 各 1 行 filters 引用 + 注释) +3. **Task 3: Phase 3 端到端验收报告** — `7a9e511` (test) — 03-VERIFICATION.md 113 行(9 truth × 32 项断言) +4. **Task 4: docs/修改记录.md 追加 Phase 3 条目** — `db4d5cf` (docs) — +22 行(5 文件 + CRED-05/06 + 跨项目联动「无」) + +**Plan metadata:** 待 final commit(本 SUMMARY + STATE.md / ROADMAP.md / REQUIREMENTS.md) + +## Files Created/Modified + +| 文件 | 操作 | 行数变化 | 说明 | +|------|------|----------|------| +| `common/logging/__init__.py` | 新建 | 0 字节(empty) | package marker | +| `common/logging/filters.py` | 新建 | 106 行 | AccessTokenMaskFilter 类 + 4 _PATTERNS + _NEEDLE 短路 + _sub/_mask_in_text/filter 方法 | +| `qy_lty/settings.py` | 修改 | LOGGING 块 +11 行(375-394 区间,filters 段 + 2 处 'filters' key) | dictConfig () 工厂语法注册 + handlers 引用 | +| `docs/修改记录.md` | 修改 | 顶部 +22 行(行 26-47 新条目) | [2026-05-08] Phase 3 条目,紧邻 [2026-05-07] Phase 2 之上 | +| `.planning/phases/03-client-and-log-mask/03-VERIFICATION.md` | 新建 | 113 行 | 9 truth × 32 项 PASS 报告 | +| `.planning/phases/03-client-and-log-mask/03-02-SUMMARY.md` | 新建 | 本文件 | 本 plan summary | + +## 防御性兜底语义说明 + +**RESEARCH 实证(已读 03-CONTEXT.md / 03-RESEARCH.md)**:当前仓库**没有**任何代码 logger 输出 `CredentialSlot.access_token` 明文: + +- `StandardResponseMiddleware`(`common/middleware.py`)只统一包装响应壳层,不打日志 +- `CredentialSlotAdminView` / `CredentialSlotClientView` 都不显式 `logger.info(serializer.data)` +- Django 默认 access log 不含请求 / 响应 body +- `aliyun_log_python_sdk` 集成(`common/aliyun_logging.py`)只 emit `record.getMessage()`,没有自动 dump request + +**所以 CRED-06 的真实价值是「防御性兜底」**,不是「修补现有泄露」: + +- Phase 1 + Phase 2 + Phase 3 的 view 让 access_token 进入「内存中可被随手 dump」的状态 +- 任何后续开发者写 `logger.info(f"PUT body: {request.data}")` 类型代码就会立即把 access_token 明文打到阿里云日志服务 +- `AccessTokenMaskFilter` 在 LOGGING.handlers 层兜底拦截,不依赖每个调用点自律 +- **未来代码改动天然安全** —— 即使新人在 view 里写 `logger.debug(self.request.data)` 也不会泄露 + +## Decisions Made + +见 frontmatter `key-decisions` 字段,已 10 条决策全部归档。 + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Pattern 4 兜底 regex 把 Pattern 3 已脱敏的输出二次 mask 吃掉末 4 位** + +- **Found during:** Task 1 跑 `_phase3_02_unit_test.py` 首次 verify +- **Issue:** Plan 给的 Pattern 4 终止符是 `[^\s,;)\]\}"\']+`,对 Query 形态 `access_token=abcdefgh1234&u=1`: + 1. Pattern 3 `(access_token=)([^&\s"\']+)` 先 match → `access_token=********1234&u=1` + 2. Pattern 4 `(access_token\s*[:=]\s*)([^\s,;)\]\}"\']+)` 再 match → 因为 `&` / `=` 不在排除集,value = `********1234&u=1`(16 字符),mask_token 输出 `************` + 末 4 位 `&u=1` = `************&u=1`,把 `1234` 吃没了 +- **Fix:** Pattern 4 终止符增加 `&` / `=` 排除字符 → `[^\s,;)\]\}"\'&=]+`;保证 Pattern 4 不再吞掉 Pattern 3 的输出尾部 +- **Files modified:** common/logging/filters.py(行 47-52,Pattern 4 regex + 5 行注释解释为什么排除 `&=`) +- **Verification:** `_phase3_02_unit_test.py` 复跑 Query 形态 OUTPUT = `'GET /x?access_token=********1234&u=1'`(含末 4 位 `1234`) +- **Committed in:** 891a5ea(与 Task 1 主体一并提交) + +**2. [Rule 1 - Bug] tuple args 形态把 `%s` 占位符当 access_token 的 value 吃掉,触发 Formatter TypeError** + +- **Found during:** Task 1 跑 `_phase3_02_unit_test.py` 第 5 项断言(tuple args) +- **Issue:** Plan 给的 filter() 实现先扫 `record.msg` 再扫 `record.args`: + 1. msg = `'access_token=%s'`,args = `('abcdefgh1234',)` + 2. 扫 msg:Pattern 4 match `access_token=` + value = `%s`(`%` 与 `s` 都不在排除集),mask_token('%s') = `**`(短于 4 全脱)。msg 变成 `'access_token=**'` + 3. 扫 args:tuple 中 `'abcdefgh1234'` 不含 `access_token` 字面量,`_NEEDLE not in text.lower()` 短路返回,不变 + 4. Formatter 阶段:`'access_token=**' % ('abcdefgh1234',)` → 占位符 `%s` 已被 `**` 替换,args 仍有 1 元素 → `TypeError: not all arguments converted during string formatting` +- **Fix:** 改 filter() 处理逻辑为: + - tuple args 形态:先 `record.getMessage()` 拼出最终字符串(msg + args % 格式化结果),再整体走 `_mask_in_text` 脱敏,最后 `record.args = None` 避免 Formatter 二次拼接 + - 其它形态(无 args / dict args):保持原 plan 逻辑分别处理 msg 和 args +- **Files modified:** common/logging/filters.py(行 79-111,filter 方法重构;增加 try/except getMessage 失败时 graceful return True 不影响其它 handler) +- **Verification:** `_phase3_02_unit_test.py` 复跑 OUTPUT = `'access_token=********1234'`(tuple args 末 4 位保留),全 9 项 ALL UNIT TESTS PASS +- **Committed in:** 891a5ea(与 Task 1 主体一并提交,因为 bug 在 Task 1 内被发现且未引入 git) + +--- + +**Total deviations:** 2 auto-fixed(Rule 1 Bug × 2) +**Impact on plan:** 两处偏差均为 plan 给的 regex / filter 实现逻辑漏洞,端到端 9 truth × 32 项 PASS 全过;plan acceptance criteria 全部达成;CRED-06 防御性兜底语义正确 + +## Issues Encountered + +无业务 / 架构问题。仅遇到上述 2 处 Plan 内置 bug,已自动修复并文档化。 + +## Stub Status + +无。本 plan 落地的 filter + LOGGING 配置全部是真实生效的代码路径: + +- `common/logging/filters.py:AccessTokenMaskFilter` 已挂载到 settings.LOGGING.handlers,T9 端到端 `logger.info` 真实输出验证脱敏生效 +- 不存在 placeholder / 硬编码 / mock 数据 / 空函数 + +## User Setup Required + +无 —— 不引入新依赖、不要求新环境变量、不需要外部服务配置;现有 `aliyun-log-python-sdk` 已在 Phase 0 安装 + +## Next Phase Readiness + +**Milestone v1.0「通用凭据槽位」至此全部完成**: + +- CRED-01(Phase 1 Plan 01-01):单例 CredentialSlot 模型 + 迁移 ✓ +- CRED-02(Phase 1 Plan 01-02):Django Admin 注册 + 脱敏 ✓ +- CRED-03(Phase 2 Plan 02-01):管理端 GET 脱敏 ✓ +- CRED-04(Phase 2 Plan 02-01):管理端 PUT 覆写 ✓ +- CRED-05(Phase 3 Plan 03-01):客户端 GET 明文 ✓ +- CRED-06(Phase 3 Plan 03-02):阿里云日志脱敏 filter ✓ + +**下一周期候选 milestone**(详见 `.planning/REQUIREMENTS.md`「候选优先级」段): + +- HIGH:ACH-02 成就解锁条件校验缺失 / SMS 验证码无频率限制 / 收紧 DEBUG / CORS_ALLOW_ALL_ORIGINS / 移除测试 MAC 硬编码 / 测试基础设施搭建(pytest 体系) +- MEDIUM:好感度 P2/P3/P4 / Python 3.8 → 3.11/3.12 升级 / 拆分 device_interaction/views.py(1867 行) + +## Self-Check: PASSED + +- [x] common/logging/__init__.py 存在,0 字节空文件 (FOUND, 0 bytes) +- [x] common/logging/filters.py 含 `class AccessTokenMaskFilter(logging.Filter)` (FOUND) +- [x] common/logging/filters.py 含 4 个 _PATTERNS 正则 (FOUND, len = 4) +- [x] qy_lty/settings.py 含 `'access_token_mask'` filter 注册 (FOUND) +- [x] qy_lty/settings.py 含 `'()': 'common.logging.filters.AccessTokenMaskFilter'` 工厂语法 (FOUND) +- [x] qy_lty/settings.py 含 `['access_token_mask']` ≥ 2 处(aliyun + console) (FOUND, count = 2) +- [x] docs/修改记录.md 顶部含 `[2026-05-08] Phase 3` 条目 (FOUND, idx=253) +- [x] docs/修改记录.md Phase 3 条目位于 Phase 2 之上 (Phase 3 idx=253 < Phase 2 idx=4465) +- [x] docs/修改记录.md Phase 3 条目含 5 处文件路径 + CRED-05 + CRED-06 (FOUND) +- [x] docs/修改记录.md Phase 3 条目跨项目联动字段写「无 — 客户端给 Unity 用」(FOUND) +- [x] qy-lty-admin/docs/修改记录.md mtime 未变(baseline 1778166405.4241624 = current) +- [x] commit 891a5ea in git log (FOUND) +- [x] commit 35eb110 in git log (FOUND) +- [x] commit 7a9e511 in git log (FOUND) +- [x] commit db4d5cf in git log (FOUND) +- [x] _phase3_02_verify.py 输出 ALL PASS(32 PASS / 0 FAIL) +- [x] _phase3_02_unit_test.py 输出 ALL UNIT TESTS PASS +- [x] DB 探针态 pk=1 / app_id='probe_app' / access_token='probe_secret_xxxx' 保持 +- [x] 5 个临时验收脚本均已删除(git status 干净) +- [x] python manage.py shell 启动无 ValueError(dictConfig 工厂语法正确) + +## TDD Gate Compliance + +本 plan frontmatter `type: execute`,非 TDD plan。无 RED/GREEN/REFACTOR 强制顺序要求。但实际操作上 Task 1 写完 filter + 单元测试同步落地,Task 1 commit 既含 filter 主体又验证 4 个正则形态 + tuple args 形态行为;与轻量 TDD 实质等价。 + +--- +*Phase: 03-client-and-log-mask* +*Plan: 02* +*Completed: 2026-05-08* +*Milestone v1.0「通用凭据槽位(APP ID + Access Token)」at this point: ALL DONE — CRED-01 to CRED-06 全部交付*