docs(03-02): 完成 Phase 3 Plan 03-02 + Milestone v1.0 收尾

- 03-02-SUMMARY.md 新建: AccessTokenMaskFilter + LOGGING + 修改记录条目落地, 9 truth × 32 项断言全 PASS, 2 处 Rule 1 auto-fix bug 文档化
- STATE.md 更新: 进度 100%, Phase 3 标记 Complete, Milestone v1.0 收尾, 决策日志补 [Plan 03-02] 7 条
- ROADMAP.md 更新: Milestone v1.0 标  完结, Phase 3 / Plan 03-01+03-02 全部 ✓
- REQUIREMENTS.md 更新: CRED-06 标记 Done, traceability 表 Phase 3 行 Done, 覆盖率 6/6 全 Done

Milestone v1.0「通用凭据槽位 (APP ID + Access Token)」CRED-01 至 CRED-06 全部交付完成。
This commit is contained in:
pmc 2026-05-08 10:36:17 +08:00
parent db4d5cf89d
commit 9965d0bcf0
4 changed files with 292 additions and 38 deletions

View File

@ -100,7 +100,7 @@
- [x] **CRED-03** 管理端 GET `/api/v1/admin/credential-slot/`admin token 鉴权(`admin_token:{token}` Redis key 体系);返回 `{ app_id, access_token: <masked>, 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-08commits 891a5ea / 35eb110 / 7a9e511 / db4d5cfAccessTokenMaskFilter 4 正则覆盖 JSON / Pyrepr / URL query / 等号或冒号兜底;端到端 9 truth × 32 项断言全 PASS
### 候选优先级(已转移自 brownfield 文档化阶段,本期不消化)
@ -142,11 +142,11 @@
| CRED-02 Django Admin 注册(脱敏 + 隐藏新增按钮) | Phase 1 凭据槽位数据层 | DonePlan 01-022026-05-07 |
| CRED-03 管理端 GETadmin token脱敏返回 | Phase 2 管理端读写接口 | DonePlan 02-01 + 02-022026-05-07 |
| CRED-04 管理端 PUTadmin token全字段覆写 + get_or_create | Phase 2 管理端读写接口 | DonePlan 02-01 + 02-022026-05-07 |
| CRED-05 客户端 GETuser token明文返回 | Phase 3 客户端读取与日志脱敏 | Pending |
| CRED-06 Access Token 阿里云日志过滤 | Phase 3 客户端读取与日志脱敏 | Pending |
| CRED-05 客户端 GETuser token明文返回 | Phase 3 客户端读取与日志脱敏 | DonePlan 03-012026-05-08 |
| CRED-06 Access Token 阿里云日志过滤 | Phase 3 客户端读取与日志脱敏 | DonePlan 03-022026-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 标记 DoneMilestone v1.0「通用凭据槽位APP ID + Access Token」CRED-01 至 CRED-06 全部交付;下一周期 milestone 候选评估见上方「候选优先级」段*

View File

@ -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-05CredentialSlotClientView 明文返回 + /api/credential-slot/ 路由注册)
- [ ] 03-02-PLAN.md — 阿里云日志 access_token 脱敏CRED-06AccessTokenMaskFilter + LOGGING 配置 + 修改记录)
- [x] 03-01-PLAN.md — 客户端凭据槽位 GET 接口CRED-05CredentialSlotClientView 明文返回 + /api/credential-slot/ 路由注册)✓ 2026-05-08commits 5269a08 / 50dcf1c / a58980f
- [x] 03-02-PLAN.md — 阿里云日志 access_token 脱敏CRED-06AccessTokenMaskFilter + LOGGING 配置 + 修改记录)✓ 2026-05-08commits 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-07Milestone v1.0「通用凭据槽位APP ID + Access Token」启动*
*最后更新2026-05-08Milestone v1.0 完结CRED-01 至 CRED-06 全部交付)*

View File

@ -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-02CRED-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 断言全 PASSMilestone 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-07Phase 2 完成Plan 02-01 + 02-02 全部交付CRED-03 / CRED-04 标记 Done端到端 8 条 success criteria 全 PASS两端修改记录互引闭环
**最后更新**: 2026-05-08Phase 3 Plan 03-02 完成AccessTokenMaskFilter 落地CRED-06 标记 Done端到端 9 truth × 32 断言全 PASSMilestone v1.0「通用凭据槽位」CRED-01~06 全部交付
## 项目引用
@ -24,26 +24,26 @@ progress:
**核心价值**:设备端与手机端通过同一个 user_id 实时互通——`device_{user_id}` 分组语义必须始终成立。
**当前重点**Milestone v1.0 通用凭据槽位APP ID + Access TokenPhase 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 02CRED-05 客户端 GET 接口)— Complete ✓
Status: Plan 03-01 完成,等待执行 Plan 03-02CRED-06 日志脱敏)
Phase: 3 of 3客户端读取与日志脱敏Complete ✓
Plan: 2 of 02CRED-06 阿里云日志脱敏)— Complete ✓
Status: Milestone v1.0 完结,等待下一周期 milestone 立项
Last activity: 2026-05-08
```
Progress: [████████░░] 83%(已完成 plan5/6 — Phase 1 全部 + Phase 2 全部 + Phase 3 Plan 01
Progress: [██████████] 100%(已完成 plan6/6 — Phase 1 全部 + Phase 2 全部 + Phase 3 全部
## 性能指标
**速度:**
- 已完成 plan 数:5
- 平均耗时:~398 s顺序执行模式
- 总执行时间:1990 sPlan 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 sPlan 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%(已完成 plan5/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 个 plan01-01184 s3 task/ 01-02~600 s4 task + checkpoint 验收)/ 02-01216 s3 task / 3 commit / 3 文件)/ 02-02~720 s2 task / 2 commit / 1 创建 + 2 修改文件 + 端到端 28 项断言)/ 03-01270 s3 task / 2 commit / 2 修改文件 + 1 临时验收脚本未入 git + 端到端 15 项断言)
- 趋势:纯 auto 代码落地 plan 速度稳定 200-270 sPlan 03-01 是 1:1 复刻 Phase 2 admin view 模板的最简形态(删 _ensure_admin / _build_response_data / def put仅保留 GET 明文),加上端到端验收,落地速度接近 Plan 02-013 处 Windows 环境兼容偏差全部 auto-fixed
- 最近 6 个 plan01-01184 s3 task/ 01-02~600 s4 task + checkpoint 验收)/ 02-01216 s3 task / 3 commit / 3 文件)/ 02-02~720 s2 task / 2 commit / 1 创建 + 2 修改文件 + 端到端 28 项断言)/ 03-01270 s3 task / 2 commit / 2 修改文件 + 1 临时验收脚本未入 git + 端到端 15 项断言)/ 03-02494 s4 task / 4 commit / 2 创建 + 2 修改文件 + 端到端 32 项断言 + 2 处 Rule 1 auto-fix bug
- 趋势:03-02 略慢于 03-01494 vs 270 s原因是 plan 内置 2 处 bugPattern 4 兜底 regex 吃尾 + tuple args 形态破坏 %s 占位符)需要现场调试 + auto-fixnett 端到端覆盖更广32 vs 15 项断言Milestone v1.0 整体节奏稳定 200-720 s/plan
*每完成一个 plan 后更新*
@ -95,6 +95,13 @@ Progress: [████████░░] 83%(已完成 plan5/6 — Phase
- **[Plan 03-01]** 客户端响应 schema 独立命名 _credential_slot_client_data_schemaaccess_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.handlersaliyun + 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 吃掉触发 TypeErrorauto-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%(已完成 plan5/6 — Phase
## 下一步
```
执行 Phase 3 Plan 02CRED-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 行 AccessTokenMaskFilter4 正则 + 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 7a9e51103-VERIFICATION.md 落地)
- Task 4`docs/修改记录.md` 顶部追加 [2026-05-08] Phase 3 条目(覆盖 5 处文件 + CRED-05/06 + 跨项目联动「无」明示commit db4d5cf
URL `/api/credential-slot/` GET 已端到端验证(明文返回 access_tokenDB 探针态保持 `probe_app` / `probe_secret_xxxx`CRED-05 已在 REQUIREMENTS.md 标记 Done
CRED-06 已在 REQUIREMENTS.md 标记 DoneDB 探针态保持 `pk=1 / app_id='probe_app' / access_token='probe_secret_xxxx'`临时验收脚本5 个)全部删除
下一步执行 Plan 03-02CRED-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-02CRED-06 日志脱敏)
Last session: 2026-05-08T02:30:22Z
Stopped at: Plan 03-02 完成CRED-06 日志脱敏 filter 落地9 truth × 32 断言全 PASSMilestone 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 完结*

View File

@ -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.AccessTokenMaskFilterlogging.Filter 子类4 正则覆盖 JSON / Pyrepr / URL query / 等号或冒号兜底)
- settings.LOGGING.filters.access_token_maskdictConfig 工厂语法 () 注册)
- settings.LOGGING.handlers.aliyun.filters / handlers.console.filtersfilter 挂在 handler 层,统一覆盖所有 logger → handler 路径)
- docs/修改记录.md 顶部 [2026-05-08] Phase 3 条目(覆盖 5 处文件 + CRED-05/06 + 跨项目联动「无」)
- .planning/phases/03-client-and-log-mask/03-VERIFICATION.md9 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.py106 行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.pyLOGGING 字典:新增 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.pyplan 03-01 创建的临时验收脚本,本 plan Task 3 末尾统一删除)
- _phase3_02_unit_test.pyTask 1 临时单元测试,验完即删)
- _phase3_02_verify.pyTask 3 端到端验收脚本,验完即删 + 输出落 03-VERIFICATION.md
- _phase3_02_settings_check.pyTask 2 验收 helper验完即删
- _phase3_admin_mtime.txtTask 4 mtime baseline 临时文件,验完即删)
key-decisions:
- "[Plan 03-02] AccessTokenMaskFilter 挂在 LOGGING.handlersaliyun + console而非 loggers 段:挂 logger 仅过滤直接通过该 logger 的 record挂 handler 才统一覆盖所有 logger → handler 路径per RESEARCH Pitfall 1"
- "[Plan 03-02] dictConfig filter 注册用 () 工厂语法不用 classdictConfig 标准对 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 吃掉触发 TypeErrorauto-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=1auto-fix Rule 1详见 Deviations"
- "[Plan 03-02] 端到端验收脚本走 python manage.py shell -c \"exec(open(...).read())\" 而非 < 重定向Windows PowerShell < 触发逐行 REPL 执行缩进块全部 IndentationErrorplan 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_URPqy-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 + console9 truth × 32 项独立断言全 PASSCRED-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:** 2qy_lty/settings.py + docs/修改记录.md
- **Files created:** 4common/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 全 PASS32 项独立断言)**
- T1-T55 truth × 14 项断言plan 03-01 客户端 GET 5 项验证user/admin token 200 + 401 × 2 + swagger schema
- T64 项filter 4 种正则形态JSON / Pyrepr / Query / Fallback伪 LogRecord 处理后含末 4 位 `1234`、不含完整明文 `abcdefgh`
- T72 项filter 不误伤 `Authorization header: bearer_user_token_xxxxxxx` / `Bearer raw_token_zzz` 等非 access_token 字段
- T85 项):端到端 admin PUT roundtrip → admin GET 脱敏 `**********RT99` + client GET 明文 `rt_secret_RT99` + app_id 一致
- T92 项):端到端 `logger.info('access_token=defensive_secret_DEFC')` → console 输出 `*****************DEFC` 脱敏(防御性兜底真实生效)
- T_FINAL2 项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-02Admin 注册)+ CRED-03admin GET 脱敏)+ CRED-04admin PUT 覆写)+ CRED-05客户端 GET 明文)+ CRED-06日志脱敏 filter全部 Done
## Task Commits
每个 task 原子提交(中文 commit message
1. **Task 1: 新建 common/logging/ 包 + AccessTokenMaskFilter**`891a5ea` (feat) — common/logging/__init__.py0 字节)+ common/logging/filters.py106 行)
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-52Pattern 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. 扫 msgPattern 4 match `access_token=` + value = `%s``%``s` 都不在排除集mask_token('%s') = `**`(短于 4 全脱。msg 变成 `'access_token=**'`
3. 扫 argstuple 中 `'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-111filter 方法重构;增加 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-fixedRule 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.handlersT9 端到端 `logger.info` 真实输出验证脱敏生效
- 不存在 placeholder / 硬编码 / mock 数据 / 空函数
## User Setup Required
无 —— 不引入新依赖、不要求新环境变量、不需要外部服务配置;现有 `aliyun-log-python-sdk` 已在 Phase 0 安装
## Next Phase Readiness
**Milestone v1.0「通用凭据槽位」至此全部完成**
- CRED-01Phase 1 Plan 01-01单例 CredentialSlot 模型 + 迁移 ✓
- CRED-02Phase 1 Plan 01-02Django Admin 注册 + 脱敏 ✓
- CRED-03Phase 2 Plan 02-01管理端 GET 脱敏 ✓
- CRED-04Phase 2 Plan 02-01管理端 PUT 覆写 ✓
- CRED-05Phase 3 Plan 03-01客户端 GET 明文 ✓
- CRED-06Phase 3 Plan 03-02阿里云日志脱敏 filter ✓
**下一周期候选 milestone**(详见 `.planning/REQUIREMENTS.md`「候选优先级」段):
- HIGHACH-02 成就解锁条件校验缺失 / SMS 验证码无频率限制 / 收紧 DEBUG / CORS_ALLOW_ALL_ORIGINS / 移除测试 MAC 硬编码 / 测试基础设施搭建pytest 体系)
- MEDIUM好感度 P2/P3/P4 / Python 3.8 → 3.11/3.12 升级 / 拆分 device_interaction/views.py1867 行)
## 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 PASS32 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 启动无 ValueErrordictConfig 工厂语法正确)
## 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 全部交付*