diff --git a/README.md b/README.md index b0f19cc..a667e0d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Python 3.12 / Django 5.2 LTS + DRF / django-admin / 用户端 Django 模板 SSR ## 当前状态 -Phase 2 计费核心已完成,T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调与 T-305 扫码充值下单 + 轮询已落地:对外 API 可用 `Authorization: Bearer ` 调用生成能力或查询点数余额;用户端 session 流程可创建充值订单、获取二维码票据并轮询订单状态,充值回调按订单幂等入账。下一步进入 Phase 4 用户端注册 / 登录。详见 [`docs/current-state.md`](docs/current-state.md)。 +Phase 2 计费核心已完成,T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调与 T-305 扫码充值下单 + 轮询已落地:对外 API 可用 `Authorization: Bearer ` 调用生成能力或查询点数余额;用户端 session 流程可创建充值订单、获取二维码票据并轮询订单状态,充值回调按订单幂等入账。Claude Code 审核后新增 T-306 安全加固,下一步先修 `image_url` SSRF 等上线前风险,再进入 Phase 4 用户端。详见 [`docs/current-state.md`](docs/current-state.md)。 > ⚠️ 涉及资金/点数。改动充值、扣费、退款、对账相关代码前,先读 [`docs/05-coding-rules.md`](docs/05-coding-rules.md) 第 8 节与 [`docs/04-architecture.md`](docs/04-architecture.md) 第四节计费时序。 diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index a2f17d1..742aab4 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -38,15 +38,15 @@ ## 当前阶段 -当前项目处于:**Phase 4 用户端起步**。Phase 2 计费核心已完成到 T-204;Phase 3 已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调与 T-305 扫码充值下单 + 轮询。下一步进入 T-501 注册 / 登录(allauth)。 +当前项目处于:**Phase 3 对外 API 安全加固**。Phase 2 计费核心已完成到 T-204;Phase 3 功能已完成 T-301 API Key 鉴权、T-302 生成标题 / 图片接口、T-303 余额查询接口、T-304 充值回调与 T-305 扫码充值下单 + 轮询;Claude Code 审核后新增 T-306 安全加固。下一步进入 T-306,先修 `image_url` SSRF,再处理 DRF 默认认证、限流和充值金额上限。 优先路径: 1. Phase 0:Django 骨架可运行、**自定义 User 模型在首次迁移前定好**、django-admin 可登录;T-004 审核修补项已完成。 2. Phase 1:最高风险功能原型 —— T-101/T-102/T-103/T-104/T-105 已完成 provider 层、模型配置表、别名解析、配置审计、录制标题/图片 smoke 与审核修补;真实图片同步耗时待配置 Fernet 主密钥、AiModel/ModelAlias 与真实上游后在 T-302/T-403 前补测。 3. Phase 2:计费核心 —— T-201/T-202/T-203 已完成 UserWallet/ApiKey/PointsLedger/CallRecord、计费规则、汇率、计费计算、并发安全扣点与失败退点。 -4. Phase 3:对外 API 与充值 —— T-301 Key 鉴权、T-302 生成接口、T-303 余额查询、T-304 充值回调、T-305 扫码下单与轮询已完成。 -5. Phase 4:用户端(Django 模板 SSR)—— 下一步 T-501 注册登录,然后 API Key 管理、个人中心/记录页、充值页。 +4. Phase 3:对外 API 与充值 —— T-301 Key 鉴权、T-302 生成接口、T-303 余额查询、T-304 充值回调、T-305 扫码下单与轮询已完成;下一步 T-306 安全加固。 +5. Phase 4:用户端(Django 模板 SSR)—— T-306 完成后进入 T-501 注册登录,然后 API Key 管理、个人中心/记录页、充值页。 6. Phase 5:后台与发布 —— 运营后台完善、完整验收、部署 / 运行文档。 ## 领取任务规则 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 9e92f20..3d87942 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -55,6 +55,7 @@ | T-303 | 余额查询接口 | T-301 | 返回余额等于流水累加;含测试 | DONE | | T-304 | 充值回调(微信/支付宝验签 + 幂等入账) | T-202 | 两端点 `@csrf_exempt`;微信 SDK 验签解密、支付宝 SDK verify;验签失败不入账;同一 order_no 重复回调只入账一次;校验回调金额与订单金额一致;使用订单创建时锁定的 `points_granted` 锁 wallet 入账写流水;补主动查单兜底;含幂等测试 | DONE | | T-305 | 扫码充值下单 + 轮询(create/status) | T-304 | 支持 weixin(native,金额分)/alipay(precreate,金额元);建 pending 订单绑定 user,并在下单时锁定汇率/预计点数→取 code_url/qr_code→前端渲染 + 轮询 status;缺商户密钥时 mock | DONE | +| T-306 | Phase 3 对外 API 安全加固 | T-305 | 按 [`phase-3-review.md`](phase-3-review.md) 处理 **P1**(`download_image_input` SSRF:协议白名单 + 私有/回环/链路本地网段拦截含重定向后地址 + 响应大小上限,含内网/元数据 URL 被拒测试)与 **P2**(全局 `REST_FRAMEWORK` 安全默认认证 + 生成/认证端点限流 + 充值金额上限);P3 仅登记媒体访问控制、完整测试补跑和过期订单口径,充值流水 DB 兜底已在 T-304 完成不要重复迁移。**P1-1 属上线前必须**;`check`/`test`/`init` 全绿并在 `../progress.md` 留证据 | TODO | ## Phase 4 · 用户端(Django 模板 SSR) diff --git a/docs/README.md b/docs/README.md index 894c8d3..4da08b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,7 @@ - [Phase 0 骨架审核](phase-0-review.md):T-001~003 代码审核结论与修补清单(P1/P2/P3),对应任务 T-004。 - [Phase 1 AI 层审核](phase-1-review.md):T-101~104 代码审核结论与修补清单(P1/P2/P3),对应任务 T-105;重点提示图片同步风险未退与 `parameters` 越权计费隐患。 - [Phase 2 计费核心审核](phase-2-review.md):T-201~203 代码审核结论(资金安全逐条核对)与加固清单(**零 P1**,P2/P3),对应任务 T-204。 +- [Phase 3 对外 API 与充值审核](phase-3-review.md):T-301~305 代码审核结论(鉴权/回调/幂等/入账逐条核对)与优化建议,对应任务 T-306;**P1 提示 `image_url` SSRF 上线前必修**。 - [API 合约](api.md):对外接口、支付回调、AI 调用模块合约、错误码。 - [路由与页面结构](routes.md):API 路由与 django-admin 后台职责。 - [环境变量与配置](env.md):Django、数据库、AI 密钥加密、支付、对象存储等配置项。 diff --git a/docs/current-state.md b/docs/current-state.md index 60b8a23..c599cfb 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -12,7 +12,7 @@ ## 当前快照 - 日期:2026-07-03 -- 阶段:Phase 4 用户端起步;Phase 3 已完成到 T-305 扫码充值下单 + 轮询,下一步 T-501 注册 / 登录(allauth) +- 阶段:Phase 3 对外 API 安全加固;Phase 3 功能已完成到 T-305,Claude Code 审核后新增 T-306 安全加固,下一步 T-306 - 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + PyMySQL 1.1.3 + cryptography 46.0.7 + requests 2.34.2 + django-admin;MySQL 8.4 已接入 settings,并支持 `MYSQL_CONNECT_TIMEOUT` / `MYSQL_READ_TIMEOUT` / `MYSQL_WRITE_TIMEOUT`;用户端(模板 SSR/Bootstrap/allauth) 后续任务落地;详见 `03-tech-stack.md` - 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104/T-105 已完成录制 title/image smoke 与审核修补;T-201 已新增 `UserWallet` / `ApiKey`、`PointsLedger` / `CallRecord`、对应 admin 与迁移;T-202 已新增 `PricingRule` / `ExchangeRate`、`apps.billing.pricing` 计费计算函数、admin 配置页与迁移;T-203 已新增 `apps.billing.services`,实现并发安全预扣、成功确认与幂等失败退点;T-204 已新增 `billing.0003_pointsledger_unique_ledger_change_type_per_call`,用 MySQL 可落地的 `ref_call + change_type` 复合唯一约束兜底防重复 refund;T-301 已新增 `apps.api.authentication.ApiKeyAuthentication` 与 `ExternalApiView`;T-302 已新增生成接口编排、序列化器、图片本地存储和 `/api/v1/generate/title|image` 路由;T-303 已新增 `apps.billing.services.get_balance_snapshot()` 与 `/api/v1/balance` 余额查询接口;T-304 已新增 `RechargeOrder`、充值回调验签适配器、幂等入账服务、微信/支付宝回调路由与迁移 `billing.0004_rechargeorder_and_more`;T-305 已新增 `create_recharge_order()`、微信/支付宝扫码下单 mock/SDK 入口、`/api/v1/recharge/create` 与 `/api/v1/recharge/status` - 测试:T-305 验证通过:`./init.ps1`、`py_compile`、`manage.py check`、`makemigrations --check`、`py -3.12 -m compileall apps config`、`py -3.12 manage.py test apps.billing apps.api --noinput --keepdb --verbosity 2`(53 tests OK),并用 `MYSQL_CONNECT_TIMEOUT=90` 补跑全量失败类 `BillingCoreModelTests` / `AdminSmokeTests`(10 tests OK)和最终下单服务补测(2 tests OK)。完整 `py -3.12 manage.py test --noinput --keepdb --verbosity 2` 本轮未作为绿灯:发现 81 tests,跑过 71 tests,其中已运行用例均无断言失败;远程 MySQL `43.128.3.240:3306` 连接超时导致 `BillingCoreModelTests.setUpClass` 与 `AdminSmokeTests.setUpClass` 失败。 @@ -21,7 +21,7 @@ - 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test` - 设计基线:**自助用户端 + 对外 API + 运营后台**三合一单体;用户模型 `User`(auth)/`UserWallet`(点数,锁 wallet 扣点)/`ApiKey`(1:N,哈希存储);对外两接口 + **能力别名 + Provider 适配器**(可插拔供应商);自助扫码充值;注册不送点数。详见 `04-architecture.md` 与 2026-06-29 / 2026-07-01 的 `progress.md` 决策 - 配置基线:运行环境变量集中见 `docs/env.md`;真实密钥/支付凭证不得写入代码或文档样例。充值订单在创建时锁定汇率与预计点数,回调入账使用订单值,不按新汇率重算。支付回调与下单由 `PAYMENT_CALLBACK_MODE` 控制:本地/测试可用 HMAC `mock`,生产应为 `sdk`;二维码本地有效期提示由 `PAYMENT_QR_EXPIRES_MINUTES` 控制。 -- 当前 blocker:远程 MySQL 连接当前不稳定,完整测试暂未单次全绿;T-305 相关测试已通过。微信/支付宝真实商户密钥/证书与生产 SDK 依赖仍待提供/安装;真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。图片同步真实耗时风险仍未退,已登记到 T-403。 +- 当前 blocker:T-306 待处理 `image_url` SSRF 上线前安全问题;远程 MySQL 连接当前不稳定,完整测试暂未单次全绿;T-305 相关测试已通过。微信/支付宝真实商户密钥/证书与生产 SDK 依赖仍待提供/安装;真实 AI 上游 smoke 需要先配置 `AI_KEY_ENCRYPTION_KEY` 并导入 AiModel/ModelAlias。图片同步真实耗时风险仍未退,已登记到 T-403。 ## 当前目录要点 @@ -43,8 +43,8 @@ - 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补;T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型;T-202 PricingRule / ExchangeRate 模型 + 计费计算;T-203 并发安全扣点 / 退点;T-204 Phase 2 计费核心审核加固;T-301 API Key 鉴权;T-302 生成标题 / 图片接口;T-303 余额查询接口;T-304 充值回调;T-305 扫码充值下单 + 轮询。 - 正在进行:无。 -- 当前 blocker:远程 MySQL 连接当前不稳定,完整测试暂未单次全绿;支付商户真实密钥/证书与生产 SDK 依赖仍待提供。 -- 下一个可领取任务:**T-501 注册 / 登录(allauth)**。 +- 当前 blocker:T-306 SSRF 安全加固待做;远程 MySQL 连接当前不稳定,完整测试暂未单次全绿;支付商户真实密钥/证书与生产 SDK 依赖仍待提供。 +- 下一个可领取任务:**T-306 Phase 3 对外 API 安全加固**。 ## 当前可运行内容 @@ -85,7 +85,7 @@ python3.12 manage.py smoke_ai_generation image --recorded 1. 读仓库级 `AGENTS.md` / `CLAUDE.md`。 2. 读 `docs/00-ai-start-here.md`。 3. 读 `docs/05-coding-rules.md`(尤其第 8 节资金安全)。 -4. 在 `docs/06-tasks.md` 领取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-501)。 +4. 在 `docs/06-tasks.md` 领取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-306)。 5. 将该任务状态改为 `DOING`。 ## 维护规则 diff --git a/docs/phase-3-review.md b/docs/phase-3-review.md new file mode 100644 index 0000000..8e3d93f --- /dev/null +++ b/docs/phase-3-review.md @@ -0,0 +1,96 @@ +# Phase 3 对外 API 与充值审核报告(T-301 ~ T-305) + +> 审核人:Claude Code(全栈视角)|日期:2026-07-03|结论:**功能验收全部达标,测试覆盖极全;但发现 1 个上线前必须处理的安全问题(SSRF)**。 +> Phase 3 是攻击面最大的一层(对外鉴权 + CSRF-exempt 回调 + 验签 + 幂等 + 资金入账)。逐条比对 `05-coding-rules.md` §8 与 `04-architecture.md` 4.1/4.2:鉴权隔离、回调验签、幂等入账、金额校验、失败退点**全部正确落地**,且有对应测试。**新增的对外风险是 `image_url` 服务端拉取的 SSRF**(P1-1)。 +> 本文面向 codex 执行:每条修补项给出「症状 / 位置 / 怎么改 / 怎么验证」。修补任务见 [`06-tasks.md`](06-tasks.md) 的 **T-306**。 + +## 一、验收核对(功能全部达标) + +| 任务 | 验收要点 | 结果 | +| --- | --- | --- | +| T-301 | Key 哈希比对定位 ApiKey→User;只挂 Key 认证不挂 Session;无效/缺失 401;用户或 Key 禁用 403 | ✅(含「web session 不被外部 API 接受」专测) | +| T-302 | 编排「别名解析+能力校验→预扣→调上游→成功确认/失败退点→写记录」;`parameters` 白名单透传;缺原图/能力错误翻 400;点数不足 402 | ✅ | +| T-303 | 余额=流水累加;含测试 | ✅ | +| T-304 | 两端点 `@csrf_exempt`;验签;同 `order_no` 幂等;金额校验;用锁定 `points_granted` 入账;主动查单兜底;幂等测试 | ✅ | +| T-305 | weixin(native)/alipay(precreate) 下单锁汇率/预计点数→取 code_url/qr_code→轮询 status;缺密钥 mock | ✅ | + +### §8 / 4.2 资金安全逐条核对 + +| 约束 | 落地 | 证据 | +| --- | --- | --- | +| 充值幂等(同 order_no 只入账一次) | ✅ | `apply_recharge_payment` 事务内 `select_for_update` 锁订单行,已 PAID 直接幂等返回 `applied=False` | +| 回调验签,失败不入账并留痕 | ✅ | mock 用 HMAC-SHA256 + `compare_digest`,真实用 SDK;验签在入账前,失败 `logger.warning` + 拒绝;均有专测 | +| 回调 `@csrf_exempt` + 主动查单兜底 | ✅ | 两回调视图 `@method_decorator(csrf_exempt)`、`authentication_classes=()`;`query_and_apply_recharge_payment` 复用同一入账路径 | +| 回调金额与订单金额一致 | ✅ | `order_amount != callback_amount → RechargeAmountMismatchError`;有「金额不一致不入账」专测 | +| 用订单锁定的 `points_granted` 入账、不重算汇率 | ✅ | 下单时 `quote_recharge_points` 锁 `exchange_rate`+`points_granted` 到订单;入账直接用订单值 | +| 对外只认 Key 不认 Session | ✅ | `ExternalApiView` 仅挂 `ApiKeyAuthentication`;有「session 登录不被接受」专测 | + +## 二、做对的(勿在修补中回退) + +1. **生成编排严格照 4.1**:`resolve→能力校验→定价→precharge→调上游→mark_success / refund`。任一上游异常先 `refund_call_points` 再抛;错误码齐(`insufficient_points`→402、`no_pricing_rule`→400、`model_not_allowed`→400、`upstream_error`→502)。所有分支(余额不足不调上游、能力不符不扣点、上游失败退点)都有测试。 +2. **鉴权隔离干净**:`ExternalApiView`(Key-only)与 `PortalSessionApiView`(Session)分离;Key 用 `sha256` 查 + `hmac.compare_digest` 定长比对;禁用 Key/User→403、缺失/无效/畸形头→401,全部有测试。`permission_denied` 覆写把认证器缺失翻成 401(而非 403)。 +3. **支付回调防护到位**:`@csrf_exempt` + 无鉴权(网关调用)+ 验签前置;**mock 是真验签**(HMAC-SHA256 + `compare_digest`,不是跳过校验);mock/真实 SDK 双路径由 `PAYMENT_CALLBACK_MODE` 切换,SDK 惰性 import 不影响未装 SDK 时启动;应答格式对(微信 `{code:SUCCESS}`、支付宝纯文本 `success`)。 +4. **入账逻辑符合 4.2**:锁订单行→幂等→pay_method 校验→金额校验→用锁定 `points_granted`→锁钱包入账写 `RECHARGE` 流水(带 `ref_order_id`)→置 PAID + txn_no + paid_at。并发重复回调经订单行锁串行化,第二次见 PAID 空操作。 +5. **Portal 会话接口自动带 CSRF**:`RechargeCreateView` 走 `SessionAuthentication`,DRF 对会话请求强制 CSRF;有「真实会话客户端强制 CSRF」专测。与 Key 接口的无 CSRF(靠签名)清晰分工。 +6. **`parameters` 透传安全**:生成层把 `parameters` 交给 provider,Phase 1 T-105 的白名单在 provider 层拦截核心/计费字段覆盖——这条链路是安全的。 +7. **存储与留痕**:生成图片用 `uuid4` 文件名(无路径穿越);`CallRecord` 不存原始 base64(专测 `does_not_store_raw_base64`);下单 CHECK 约束 `amount/exchange_rate/points_granted > 0`。 +8. **测试覆盖为四阶段最全**:鉴权 8 + 余额 3 + 回调 5(验签/金额/幂等/csrf-exempt)+ 下单查单 6(含 CSRF 强制、owner-only、主动查单入账)+ 生成 9(含上游失败退点)+ billing recharge(幂等/金额/重复充值 DB 约束)。 +9. **向后兼容**:`aliases.resolve_alias` 保留(委托给新的 `resolve_model_alias`),Phase 1 的 smoke 命令未被破坏。 + +## 三、优化建议 / 修补清单 + +### P1 · 上线前必须处理(安全) + +#### P1-1 SSRF:`image_url` 让服务端拉取调用方任意 URL + +- **症状**:`generate_title/image` 支持传 `image_url`,`load_image_input → download_image_input(url)` 会用服务端发起 GET。当前仅 `URLField` 校验格式 + 事后 content-type 检查,**没有**内网/回环/链路本地拦截。自助注册用户可让服务器请求 `http://169.254.169.254/latest/meta-data/`(云元数据)、`http://127.0.0.1:3306`、内网服务等。**更糟的是**:`load_image_input` 在 `precharge` **之前**执行,所以 **0 点数用户也能无成本触发** SSRF。另外无响应大小上限(可拉超大文件→内存耗尽 DoS)、无重定向限制(可 30x 绕过校验 / DNS rebinding)。 +- **位置**:`apps/api/generation.py:download_image_input`(及 `load_image_input`)。 +- **怎么改**: + 1. 只允许 `http`/`https`; + 2. 解析主机→IP,**拒绝私有/回环/链路本地/保留网段**(`ipaddress.ip_address(...).is_private/is_loopback/is_link_local/is_reserved`),并对**重定向后的最终地址**与**DNS 解析结果**都校验(防 30x 绕过与 rebinding),或直接 `allow_redirects=False`; + 3. 加响应大小上限(如 stream + 累计字节阈值,超限即断)与显式连接/读取超时(读超时已有); + 4. 可选:加白名单域 / 关闭 `image_url` 只保留 `image_base64`(若产品不需要远程图)。 +- **验证**:新增测试——`image_url=http://127.0.0.1/`、`http://169.254.169.254/` 一律 400 且不发起真实内网请求(可 mock 解析层断言被拦);超大响应被截断;30x 跳到内网被拒。 + +### P2 · 建议处理 + +#### P2-1 无全局 DRF 安全默认,靠每视图显式设置(脚枪) + +- **症状**:`settings.py` 无 `REST_FRAMEWORK` 块,DRF 默认 `DEFAULT_AUTHENTICATION_CLASSES = [Session, Basic]`。当前外部视图都显式覆盖为 `ApiKeyAuthentication`(安全),但**将来任何新增的 `APIView` 若忘了设置,会默默继承 Session+Basic 默认**——对「只认 Key」的对外服务是隐患。 +- **位置**:`config/settings.py`。 +- **怎么改**:显式配置 `REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": [], "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"]}`(默认空认证,强制每视图显式声明),Portal/外部视图各自 opt-in;顺带把 `DEFAULT_THROTTLE_*` 一并配上(见 P2-2)。 +- **验证**:加一条测试——一个不显式设 `authentication_classes` 的 APIView 默认不接受 session。 + +#### P2-2 生成 / 鉴权端点无限流 + +- **症状**:付费且调上游的 `generate` 接口、以及 401 认证失败**无任何节流**。存在刷点/放大上游成本/Key 枚举探测的空间。Backlog 有「限流细化」,但**生成端点建议上线前就加**。 +- **位置**:`config/settings.py` + `apps/api/views.py`。 +- **怎么改**:配 DRF `ScopedRateThrottle`,给生成接口一个较严 scope(按用户/Key),认证失败按 IP 限流。 +- **验证**:超过阈值返回 429。 + +#### P2-3 充值金额无上限 + +- **症状**:`RechargeCreateRequestSerializer.amount` 只有 `min_value=0.01`,无上限(`max_digits=12` 理论到百亿)。易因误操作/异常产生超大额 pending 订单。 +- **位置**:`apps/api/serializers.py`。 +- **怎么改**:加单笔 `max_value`(按业务定,如 100000 元)。 +- **验证**:超上限返回 400。 + +### P3 · 登记 / 后续 + +- **充值流水 DB 兜底已存在,非待办**:T-304 已在 `billing.0004_rechargeorder_and_more` 落地 `unique(ref_order_id, change_type)`,并有重复充值流水 `IntegrityError` 测试;后续修补 T-306 时不要重复增加同类迁移。 +- **生成图片以 `/media` 公网 URL 暴露**:`uuid4` 不可猜但无 per-user 访问控制。MVP 可接受;敏感场景需签名 URL / 鉴权代理。 +- **完整测试套件在 T-305 后未再单次全绿**:受远程 MySQL `43.128.3.240` 不稳定影响,靠分 app 子集通过;T-302 时有 60 tests 单次全绿记录。建议 DB 稳定后补一次 T-305 后的完整全绿。 +- **过期订单不自动置 `expired`**:属有意设计(避免误挡延迟到达的真实回调),二维码过期仅前端提示,入账仍以回调/查单为准。合理,登记备忘。 + +## 四、说明:未本地复跑 + +审核机(WSL)无 `python3.12`,**未本地复跑**。本报告基于静态审查(`api/authentication`、`generation`、`views`、`serializers`、`storage`、`urls`;`billing/services`、`payment_gateways`、`models`、`migrations`;`settings`;两 app `tests`)+ codex 执行记录(T-301 52 tests / T-302 60 tests 均单次全绿;T-303~305 分 app 子集通过,完整套件受远程 MySQL 连接稳定性阻塞,失败点为连接超时非断言失败)。 + +T-306 处理后,请重跑 `check`/`test`/`init` 并把证据记入 `progress.md`(`06-tasks.md` 使用规则第 5 条)。 + +## 五、T-306 完成定义 + +- **P1-1** 已处理:`download_image_input` 加协议白名单 + 私有/回环/链路本地网段拦截(含重定向后地址)+ 响应大小上限;含「内网 URL/元数据地址被拒、超大响应被截断」测试。 +- **P2-1/P2-2/P2-3** 已处理:全局 `REST_FRAMEWORK` 安全默认 + 生成/认证限流 + 充值金额上限;各含测试。 +- **P3** 各项已在 `06-tasks.md` 对应任务或 Backlog 登记,不遗失。 +- `check` 0 issues、`test` 全绿(建议一次完整套件)、`init` 通过,证据入 `progress.md`。 diff --git a/progress.md b/progress.md index c6e80ff..ee62e0b 100644 --- a/progress.md +++ b/progress.md @@ -677,3 +677,29 @@ - 阻塞:T-305 功能与相关测试无阻塞;完整测试仍被远程 MySQL 连接稳定性阻塞。真实支付生产下单 / 查单仍需要安装 SDK 并提供微信、支付宝商户密钥/证书与公网 notify_url;当前 mock 用于本地/测试联调。 - 决策:状态轮询不因本地 `expires_at` 自动把 pending 订单改 expired,避免误挡延迟到达的真实支付回调;二维码过期只作为前端提示,支付结果仍以回调或主动查单入账为准。T-305 只提供服务端 create/status,实际用户端充值页面留给 T-504。 - 下一步:领取 T-501 注册 / 登录(allauth)。 + +## 2026-07-03 Phase 3 对外 API 与充值审核(Claude Code review,非任务) + +- 状态:DONE(审核完成) +- 变更:新增 `docs/phase-3-review.md`;`06-tasks.md` 新增 T-306 安全加固任务;`docs/README.md` 导航登记。 +- 审核结论:**T-301~305 功能验收全部达标,测试覆盖为四阶段最全**。§8/4.2 逐条核对通过——对外只认 Key 不认 Session(含专测)、回调 @csrf_exempt + 验签前置(mock 用 HMAC-SHA256+compare_digest 真验签、mock/真实 SDK 双路径由 PAYMENT_CALLBACK_MODE 切换、SDK 惰性导入)、入账锁订单行幂等 + 金额校验 + 用订单锁定 points_granted + 锁钱包写 RECHARGE 流水、主动查单兜底复用同一入账、生成编排 4.1 全分支(余额不足不调上游/能力不符不扣点/上游失败先退点)均有测试;Portal 会话接口自动强制 CSRF(含专测);resolve_alias 保留向后兼容未破 smoke。 +- 发现(详见 `phase-3-review.md`): + - P1-1(上线前必修,安全):`download_image_input` 拉取调用方任意 `image_url` = SSRF;且在 precharge **之前**执行,0 点数用户即可无成本触发;无内网/回环/link-local 拦截、无大小上限、无重定向限制(可 30x/DNS rebinding 绕过)。须加协议白名单 + 网段拦截(含重定向后地址)+ 大小上限 + 测试。 + - P2-1:settings 无全局 REST_FRAMEWORK 默认,DRF 默认认证是 Session+Basic;外部视图都显式覆盖为 ApiKey(当前安全),但将来新增视图忘设置会默默继承默认——建议设 DEFAULT_AUTHENTICATION_CLASSES=[] 强制显式声明。 + - P2-2:生成/认证端点无限流(刷点/放大上游成本/Key 枚举风险),建议 ScopedRateThrottle。 + - P2-3:充值金额只有 min 无 max,建议加单笔上限。 + - P3(登记):充值流水 DB 兜底已存在(T-304 已加 `unique(ref_order_id, change_type)`,并有重复充值流水 `IntegrityError` 测试),非待办;生成图片 /media 公网 URL 无 per-user 访问控制;T-305 后完整套件未再单次全绿(远程 MySQL 不稳,T-302 时有 60 tests 全绿);过期订单不自动置 expired(有意设计)。 +- 未本地复跑:审核机无 python3.12,结论基于静态审查(api authentication/generation/views/serializers/storage/urls;billing services/payment_gateways/models/migrations;settings;两 app tests)+ codex 执行记录(T-301 52 / T-302 60 tests 单次全绿;T-303~305 分 app 子集通过,完整套件受远程 MySQL 连接稳定性阻塞,失败点为连接超时非断言失败)。 +- 口径校正:后续核对发现报告 P3 把充值流水 DB 兜底误列为待办;T-304 已在 `billing.0004_rechargeorder_and_more` 落地 `unique(ref_order_id, change_type)`,并有重复充值流水 `IntegrityError` 测试。后续文档已改为“DB 兜底已存在,非待办”。 +- 下一步:codex 领 T-306 做 P1 SSRF 修复 + P2 加固(P1 属上线前必须)。 + +## 2026-07-03 Phase 3 审核报告口径修正(文档) + +- 状态:DONE +- 变更: + - 修正 `docs/phase-3-review.md`:P3 中充值流水 DB 兜底口径改为“已存在,非待办”,明确 T-306 不应重复增加同类迁移。 + - 修正 `progress.md` 中同一处错误口径。 + - 更新 `README.md`、`docs/00-ai-start-here.md`、`docs/current-state.md`:下一步从 T-501 改为 T-306。 + - 更新 `docs/06-tasks.md`:T-306 验收口径明确 P3 只登记媒体访问控制、完整测试补跑和过期订单口径。 +- 验证:文档口径修正,无代码变更;执行 `git diff --check` 作为格式检查。 +- 下一步:领取 T-306 Phase 3 对外 API 安全加固。