docs: 合并 smtp 安全审计复审(0809)进初版(0801)
- 将 0809 复审新增的 7 项发现(P1-F1/P2-F2/P2-F3/P3-F4~F7)及测试盲区章节 并入 smtp_SECURITY_AUDIT_20260801.md,形成统一合并版(保留初版验证记录与 已修复结论) - 重复的 0809 复审文档(本地未被 git 跟踪)已删除 - docs/INDEX.md smtp 行指回合并版并标注'初版+复审合并版'
This commit is contained in:
1 parent
63cfdd2066
commit
29e4f96061
2 files changed
+309
-48
No files matched your search
+1
-1
@@ -48,7 +48,7 @@ docs/
|
||||
| static | [static_SECURITY_AUDIT_20260801.md](security/static_SECURITY_AUDIT_20260801.md) | Python 前端托管/文件服务 |
|
||||
| fsys | [fsys_SECURITY_AUDIT_20260808.md](security/fsys_SECURITY_AUDIT_20260808.md) | Python 文件系统服务 |
|
||||
| ai_review | [ai_review_SECURITY_AUDIT_20260801.md](security/ai_review_SECURITY_AUDIT_20260801.md) | Python AI 内容审核(DeepSeek + RAG) |
|
||||
| smtp | [smtp_SECURITY_AUDIT_20260801.md](security/smtp_SECURITY_AUDIT_20260801.md) | Python 邮件服务(验证码发送/校验) |
|
||||
| smtp | [smtp_SECURITY_AUDIT_20260801.md](security/smtp_SECURITY_AUDIT_20260801.md) | Python 邮件服务(验证码发送/校验)初版 + 0809 复审合并版 |
|
||||
| captcha | [captcha_SECURITY_AUDIT_20260801.md](security/captcha_SECURITY_AUDIT_20260801.md) | Python 验证码服务 |
|
||||
| confs | [confs_SECURITY_AUDIT_20260801.md](security/confs_SECURITY_AUDIT_20260801.md) | Python 统一配置服务 |
|
||||
| libmsg | [libmsg_SECURITY_AUDIT_20260806.md](security/libmsg_SECURITY_AUDIT_20260806.md) | C++ RPC 基础库(pymsg 协议帧、MsgClient/Server、RpcClient/Server) |
|
||||
|
||||
@@ -1,47 +1,70 @@
|
||||
# smtp 模块安全审计报告
|
||||
# smtp 模块安全审计报告(合并版)
|
||||
|
||||
**审计日期**: 2026-08-01
|
||||
**审计范围**: `smtp.py`(旧版)、`smtp_v2.py`(新版)、`log_client.py`
|
||||
**代码版本**: dev_qing 分支
|
||||
**初版审计日期**: 2026-08-01
|
||||
**复审日期**: 2026-08-09
|
||||
**审计范围**: `smtp.py`(旧版,已下线)、`smtp_v2.py`、`log_client.py`、`smtp/README.md`、`tests/blackbox/test_smtp.py`
|
||||
**代码版本**: `main` @ `63cfdd2`(fast-forward 自 `23a8559`)
|
||||
**审计方式**: 静态代码审查 + 与 gateway 集成路径回溯
|
||||
**合并说明**: 本文件为 2026-08-01 初版与 2026-08-09 复审的合并版。初版结论中仍成立的条目保留,复审新增的 7 项发现(P1-F1、P2-F2、P2-F3、P3-F4~F7)并入对应等级章节。原复审文档 `smtp_SECURITY_AUDIT_20260809.md` 已并入本文件并删除。
|
||||
|
||||
---
|
||||
|
||||
## 代码验证记录
|
||||
## 一、结论速览
|
||||
|
||||
**验证日期**: 2026-08-03
|
||||
**验证方式**: 逐条对照审计报告与当前代码(`smtp_v2.py`、`smtp.py`、`log_client.py`、`_run.sh`)
|
||||
| 等级 | 初版(0801) | 复审新增(0809) | 当前合计 | 已修复 | 未修复 |
|
||||
|------|-----------|---------------|---------|--------|--------|
|
||||
| P0 | 2 | 0 | 2 | 1 | 1 |
|
||||
| P1 | 2 | 1 | 3 | 1 | 2 |
|
||||
| P2 | 0 | 2 | 2 | 0 | 2 |
|
||||
| P3 | 0 | 4 | 4 | 0 | 4 |
|
||||
|
||||
### 验证结果汇总
|
||||
复审**新发现 7 项**,其中最关键的是 **P1-F1:`now` 变量遮蔽导致 SMTP 发送失败时限流/熔断回滚失效**——一个文档已声明"失败不计入配额"但实际未生效的**失效安全控制**,可被用于配额耗尽型拒绝服务。
|
||||
|
||||
| 等级 | 总数 | 已修复 | 部分修复 | 未修复 | 修复率 |
|
||||
|------|------|--------|----------|--------|--------|
|
||||
| P0 | 2 | 1 | 0 | 1 | 50% |
|
||||
| P1 | 2 | 1 | 0 | 1 | 50% |
|
||||
| **合计** | **4** | **2** | **0** | **2** | **50%** |
|
||||
|
||||
### 逐条验证
|
||||
|
||||
| 编号 | 状态 | 代码位置 | 验证说明 |
|
||||
|------|------|----------|----------|
|
||||
| P0-1 | ✅ 已修复 | ~~`smtp.py`~~ | 旧版 `smtp.py` 已删除下线(2026-08-04),杜绝误启动;`_run.sh` 使用 `smtp_v2.py` |
|
||||
| P0-2 | ❌ 未修复 | `smtp_v2.py` L113-117 | 使用 pymsg RpcServer,无认证机制(与 pymsg P0-3 同源),依赖 127.0.0.1 网络隔离 |
|
||||
| P1-1 | ❌ 未修复 | `smtp_v2.py` L90 | 验证码存储在进程内存 `code_store` dict,服务重启后全部失效 |
|
||||
| P1-2 | ✅ 已修复 | `smtp_v2.py` L82, L191 | `SMTP_HOURLY_LIMIT` 从环境变量正确加载(默认 100),全局熔断逻辑正确生效 |
|
||||
> 初版审计 **未验证** `send_email` 失败路径下的限流/熔断回滚,亦未覆盖 OTP 随机源、日志打印验证码等问题——这正是复审新增发现所在。
|
||||
|
||||
---
|
||||
|
||||
## 修复进度
|
||||
## 二、代码验证记录
|
||||
|
||||
**初版验证日期**: 2026-08-03
|
||||
**初版验证方式**: 逐条对照初版审计与当时代码(`smtp_v2.py`、`smtp.py`、`log_client.py`、`_run.sh`)
|
||||
|
||||
### 初版验证结果汇总(0803)
|
||||
|
||||
| 等级 | 总数 | 已修复 | 未修复 | 修复率 |
|
||||
|------|------|--------|--------|--------|
|
||||
| P0 | 2 | 1 | 1 | 50% |
|
||||
| P1 | 2 | 1 | 1 | 50% |
|
||||
|
||||
### 复审状态(0809)
|
||||
|
||||
| 编号 | 初版结论 | 复审复核 | 说明 |
|
||||
|------|--------|---------|------|
|
||||
| P0-1 | 旧 `smtp.py` 已删除下线 ✅ | 维持已修复 | `_run.sh` 启动 `smtp_v2.py`,无旧版残留风险 |
|
||||
| P0-2 | pymsg 无认证,依赖 127.0.0.1 隔离 ❌ | **维持未修复** | 与所有 pymsg 服务同源,风险由网络隔离承担(见 §九) |
|
||||
| P1-1 | 验证码存内存,重启失效 ❌ | **维持未修复** | 单实例设计下的可用性弱点(见 §九) |
|
||||
| P1-2 | `SMTP_HOURLY_LIMIT` 生效 ✅ | 维持已修复 | 默认值 100/h 正确加载 |
|
||||
| P1-F1 | —(初版未覆盖) | **新增未修复** | `now` 遮蔽导致失败回滚失效(见 §五) |
|
||||
| P2-F2 | —(初版未覆盖) | **新增未修复** | 验证码明文入 Logs 表(见 §六) |
|
||||
| P2-F3 | —(初版未覆盖) | **新增未修复** | `random` 弱随机源(见 §六) |
|
||||
| P3-F4~F7 | —(初版未覆盖) | **新增未修复** | 见 §七 |
|
||||
|
||||
---
|
||||
|
||||
## 三、修复进度
|
||||
|
||||
| 优先级 | 总数 | 已修复 | 待修复 |
|
||||
|--------|------|--------|--------|
|
||||
| P0 | 2 | 2 | 0 |
|
||||
| P1 | 2 | 1 | 1 |
|
||||
| P0 | 2 | 1 | 1 |
|
||||
| P1 | 3 | 1 | 2 |
|
||||
| P2 | 2 | 0 | 2 |
|
||||
| P3 | 4 | 0 | 4 |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 P0 — 严重问题
|
||||
## 四、🔴 P0 — 严重问题
|
||||
|
||||
### P0-1: 旧版 smtp.py 存在大量致命漏洞(需立即下线) ✅ 已修复
|
||||
### P0-1: 旧版 smtp.py 存在大量致命漏洞(已立即下线) ✅ 已修复
|
||||
- **位置**: ~~`smtp.py`~~、`_run.sh`
|
||||
- **问题**: 旧版 smtp.py 缺乏关键防护:
|
||||
1. 无发送速率限制(可被用于邮件炸弹)
|
||||
@@ -53,38 +76,276 @@
|
||||
- **修复 (2026-08-04)**: 旧版 `smtp.py` 已**删除下线**,杜绝误启动;README 中相关描述已同步更新
|
||||
|
||||
### P0-2: 依赖 pymsg 无认证(缓解:绑定 127.0.0.1)
|
||||
- **说明**: 与其他 pymsg 服务相同,当前依赖网络层隔离
|
||||
- **位置**: `smtp_v2.py` L113-117
|
||||
- **说明**: 使用 pymsg RpcServer,无认证机制(与 pymsg P0-3 同源),依赖 127.0.0.1 网络隔离。当前风险由网络隔离承担,建议在 `SECURITY_HARDENING_PLAN` 的内网 mTLS/共享密钥方案中统一解决。
|
||||
|
||||
---
|
||||
|
||||
## 🟠 P1 — 高危问题
|
||||
## 五、🟠 P1 — 高危问题
|
||||
|
||||
| # | 问题 | 说明 |
|
||||
|---|------|------|
|
||||
| P1-1 | 验证码存储在进程内存,服务重启后所有未使用验证码失效 | 可被利用造成拒绝服务(频繁重启导致用户无法注册) |
|
||||
| P1-2 | 全局发送熔断配置需确认生效(`SMTP_HOURLY_LIMIT`) | smtp_v2 已有但需验证环境变量正确加载 |
|
||||
| # | 问题 | 状态 | 说明 |
|
||||
|---|------|------|------|
|
||||
| P1-1 | 验证码存储在进程内存,服务重启后所有未使用验证码失效 | ❌ 未修复 | 可被利用造成拒绝服务(频繁重启导致用户无法注册) |
|
||||
| P1-2 | 全局发送熔断配置生效(`SMTP_HOURLY_LIMIT`) | ✅ 已修复 | smtp_v2 已有且环境变量正确加载(默认 100/h) |
|
||||
| P1-F1 | `now` 变量遮蔽,发送失败时限流/熔断回滚失效 | ❌ 未修复 | 见下详述 |
|
||||
|
||||
### P1-1: 验证码存进程内存(详情)
|
||||
- **位置**: `smtp_v2.py` L90
|
||||
- 验证码存储在进程内存 `code_store` dict,服务重启后全部失效。单实例设计下可接受;若需多实例/高可用应引入集中式存储(Redis 等)并配失效通知。
|
||||
|
||||
### P1-F1: `now` 变量遮蔽,导致 SMTP 发送失败时的限流/熔断回滚失效
|
||||
|
||||
- **位置**: `smtp_v2.py` `L176`、`L210`、`L233`–`L238`
|
||||
- **性质**: 失效的安全控制(配额耗尽型可用性风险)
|
||||
- **严重程度**: P1(高)
|
||||
|
||||
**问题描述**
|
||||
|
||||
`_handle_send_email` 在邮箱级限流预占阶段记录时间戳:
|
||||
|
||||
```python
|
||||
# L176
|
||||
now = time.time()
|
||||
...
|
||||
# L177-199(在 with STORE_LOCK 内)
|
||||
log['hour'].append(now) # L197
|
||||
log['day'].append(now) # L198
|
||||
global_send_log.append(now) # L199
|
||||
```
|
||||
|
||||
随后在写入验证码的锁块内**重新赋值**了同名变量:
|
||||
|
||||
```python
|
||||
# L205-213(另一段 with STORE_LOCK)
|
||||
code_store[email] = {...}
|
||||
now = time.time() # L210 ← 覆盖了 L176 的 now
|
||||
expired = [k for k, v in code_store.items() if v['expires_at'] < now]
|
||||
```
|
||||
|
||||
而在 SMTP 发送失败的回滚分支中,比较使用的是 **L210 之后的 `now`**:
|
||||
|
||||
```python
|
||||
# L227-238(except 块内,with STORE_LOCK)
|
||||
log = send_log.get(email)
|
||||
if log:
|
||||
if log['hour'] and log['hour'][-1] == now: # L233 ← now 已是 L210 的值
|
||||
log['hour'].pop()
|
||||
if log['day'] and log['day'][-1] == now: # L236
|
||||
log['day'].pop()
|
||||
if global_send_log and global_send_log[-1] == now: # L237
|
||||
global_send_log.pop()
|
||||
```
|
||||
|
||||
由于 `time.time()` 在 L176 与 L210 之间至少相隔毫秒级,二者浮点值**永不相等**,因此 `==` 比较恒为 `False`,回滚分支**永远不执行 pop**。
|
||||
|
||||
**影响**
|
||||
|
||||
1. **邮箱级配额被失败消耗**:单次 SMTP 发送失败(网络抖动、上游服务商故障)会让该邮箱的 `3次/小时`、`10次/天` 名额白白减少——与 README 明确声明的"失败不计入配额"语义**直接矛盾**。
|
||||
2. **全局熔断计数器被失败消耗**:`global_send_log`(默认 100 封/小时)也会因失败而增长。一次短暂的 SMTP 故障即可把全局计数推过阈值,触发全局熔断,**阻断全体用户的注册验证码发送长达 1 小时**。
|
||||
3. **可用性 DoS 面**:若 SMTP 处于不稳定状态,正常用户与攻击流量都会"烧"掉配额,且恢复后仍需等待窗口滑动;属于外部依赖故障被放大为平台级可用性事故。
|
||||
|
||||
> 注:冷却回滚(`send_history.pop`,L229)使用的是独立字典、不经 `now` 比较,**该部分仍正确**;仅限流/熔断预占回滚被破坏。
|
||||
|
||||
**修复建议**
|
||||
|
||||
引入独立变量承载预占时间戳,并在过期清理处使用局部变量,避免遮蔽:
|
||||
|
||||
```python
|
||||
# —— 限流预占阶段(原 L176 / L197-199)——
|
||||
preempt_ts = time.time()
|
||||
...
|
||||
log['hour'].append(preempt_ts)
|
||||
log['day'].append(preempt_ts)
|
||||
global_send_log.append(preempt_ts)
|
||||
|
||||
# —— 验证码写入阶段(原 L210),过期清理用局部变量,不再覆盖 preempt_ts ——
|
||||
cleanup_now = time.time()
|
||||
expired = [k for k, v in code_store.items() if v['expires_at'] < cleanup_now]
|
||||
|
||||
# —— 失败回滚阶段(原 L233-238),与预占使用同一 preempt_ts ——
|
||||
if log['hour'] and log['hour'][-1] == preempt_ts:
|
||||
log['hour'].pop()
|
||||
if log['day'] and log['day'][-1] == preempt_ts:
|
||||
log['day'].pop()
|
||||
if global_send_log and global_send_log[-1] == preempt_ts:
|
||||
global_send_log.pop()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已有安全机制(smtp_v2.py)
|
||||
## 六、🟠 P2 — 中危问题
|
||||
|
||||
1. ✅ **同邮箱 60 秒发送冷却**: 冷却期拒绝并返回 `retry_after`
|
||||
2. ✅ **邮箱级限流**(gateway 层双窗口:3次/小时 + 10次/天)
|
||||
3. ✅ **验证码一次性消费**: 校验通过立即删除,防重放
|
||||
4. ✅ **连续失败 5 次自动作废**: 防暴力破解验证码
|
||||
5. ✅ **10 分钟有效期**: 惰性过期清理
|
||||
6. ✅ **发送失败不计入冷却**: 防止网络错误误限制
|
||||
7. ✅ **全局熔断(可选)**: `SMTP_HOURLY_LIMIT` 默认 100 封/小时
|
||||
8. ✅ **fail-closed**: SMTP 异常时验证码校验失败
|
||||
### P2-F2:验证码明文写入集中日志(Logs 表)造成信息泄露
|
||||
|
||||
- **位置**: `smtp_v2.py` `L250`
|
||||
- **性质**: 敏感凭据泄露
|
||||
- **严重程度**: P2(中)
|
||||
|
||||
**问题描述**
|
||||
|
||||
```python
|
||||
logs.info("smtp", f"{email}: 验证码发送成功({code})") # L250
|
||||
```
|
||||
|
||||
`log_client` 会把该消息经 pymsg 异步 INSERT 进 `db` 服务的 `Logs` 表(`log_client.py` L214-224)。验证码在 10 分钟有效期内以**明文**持久化在数据库,任何能读取 `db.Logs` 的角色(运维、具备数据库访问权的内部服务、被攻陷的依赖)都可在有效期内**批量收割有效验证码**,直接绕过邮箱验证。
|
||||
|
||||
**影响**
|
||||
|
||||
- 绕过注册/找回密码的邮箱验证环节,危害等同验证码接口暴露。
|
||||
- 违反 OTP 处理最佳实践(验证码不应以可恢复形式落盘)。
|
||||
|
||||
**修复建议**
|
||||
|
||||
日志中去除验证码明文,仅记录事件与邮箱:
|
||||
|
||||
```python
|
||||
logs.info("smtp", f"{email}: 验证码发送成功") # 去掉 ({code})
|
||||
```
|
||||
|
||||
如确需可追溯,可记入验证码的哈希(如 `hashlib.sha256(code.encode()).hexdigest()[:8]`),但推荐不记录。
|
||||
|
||||
### P2-F3:验证码使用非密码学安全随机源 `random`
|
||||
|
||||
- **位置**: `smtp_v2.py` `L201`
|
||||
- **性质**: 弱随机源(OTP 可预测性)
|
||||
- **严重程度**: P2(中,纵深防御)
|
||||
|
||||
**问题描述**
|
||||
|
||||
```python
|
||||
code = "".join(str(random.randint(0, 9)) for _ in range(CODE_LEN)) # L201
|
||||
```
|
||||
|
||||
`random` 基于 Mersenne Twister(MT19937),**非密码学安全** PRNG。虽然 6 位码空间本身仅 10⁶,叠加 `MAX_VERIFY_ATTEMPTS=5` 与 60s 冷却限制了在线爆破,但使用可预测 PRNG 进一步削弱了有效熵,属于不合规的口令生成方式。
|
||||
|
||||
**修复建议**
|
||||
|
||||
改用 `secrets` 模块:
|
||||
|
||||
```python
|
||||
import secrets
|
||||
code = "".join(secrets.choice("0123456789") for _ in range(CODE_LEN))
|
||||
# 或:code = f"{secrets.randbelow(10**CODE_LEN):0{CODE_LEN}d}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修复路线图
|
||||
## 七、🟡 P3 — 低危问题(纵深防御)
|
||||
|
||||
### 立即(24小时)
|
||||
- [x] 确认 smtp 服务运行的是 smtp_v2.py,旧版下线 ✅ 已确认(`_run.sh` 使用 `smtp_v2.py`)
|
||||
- [x] 删除或隔离 smtp.py 防止误用 ✅ 已删除(2026-08-04)
|
||||
### P3-F4:验证码比较非常数时间
|
||||
|
||||
- **位置**: `smtp_v2.py` `L271`
|
||||
- **性质**: 理论时序侧信道
|
||||
- **严重程度**: P3(低)
|
||||
|
||||
```python
|
||||
if entry and entry['code'] == code: # L271
|
||||
```
|
||||
|
||||
普通 `==` 字符串比较为短路比较,理论上存在微小时序差。当前每次错误尝试都会递增 `attempts` 并在 5 次后作废,实际可利用性极低,但使用常数时间比较是 OTP 校验的规范做法。
|
||||
|
||||
**修复建议**:
|
||||
|
||||
```python
|
||||
import hmac
|
||||
if entry and hmac.compare_digest(entry['code'], code):
|
||||
...
|
||||
```
|
||||
|
||||
### P3-F5:smtp 内部邮箱格式校验过弱
|
||||
|
||||
- **位置**: `smtp_v2.py` `L153`
|
||||
- **性质**: 输入校验(纵深防御)
|
||||
- **严重程度**: P3(低)
|
||||
|
||||
```python
|
||||
if not email or "@" not in email: # L153
|
||||
```
|
||||
|
||||
仅检查是否含 `@`,接受 `"a@b"` 这类无 TLD 的非法地址。gateway 层已用严格正则(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)校验,smtp 仅监听 127.0.0.1,故实际风险低;但作为内部服务至少应与上游保持一致的正则,避免畸形地址被传入 `smtplib`。
|
||||
|
||||
### P3-F6:调用方可控 `subject`/`content` 无长度上限
|
||||
|
||||
- **位置**: `smtp_v2.py` `L202`–`L203`
|
||||
- **性质**: 资源滥用(纵深防御)
|
||||
- **严重程度**: P3(低)
|
||||
|
||||
```python
|
||||
subject = str(req.get("subject") or DEFAULT_SUBJECT)
|
||||
content = str(req.get("content") or DEFAULT_CONTENT).replace("{code}", code)
|
||||
```
|
||||
|
||||
`subject`/`content` 来自请求体且无长度限制。当前仅 gateway 以固定参数调用(不传这两个字段),且 smtp 仅本机可达,风险低;一旦调用方或网络边界变化,可被用于构造超大邮件载荷。建议加长度上限(如 256 / 4096 字节)。
|
||||
|
||||
### P3-F7:失败响应直接回传上游 SMTP 错误原文
|
||||
|
||||
- **位置**: `smtp_v2.py` `L240`
|
||||
- **性质**: 内部信息暴露(已被 gateway 边缘掩码)
|
||||
- **严重程度**: P3(低)
|
||||
|
||||
```python
|
||||
return {"sent": False, "error": f"验证码发送失败:{err}"} # L240
|
||||
```
|
||||
|
||||
`{err}` 包含上游 SMTP 服务器返回的具体错误(如认证扩展、连接拒绝原因),属内部细节。当前 `gateway/src/main.cpp`(`handleSendEmailCode`,L2075-2083)仅在 `retry_after>0`(冷却)时透传 `smtp_err`,其余失败统一返回通用 `3103`,故 **HTTP 边缘已掩码**。但该内部细节仍会出现在 smtp 自身日志与任何直接消费 pymsg 响应的场景;若 smtp 未来被放开监听或日志被外发,即构成泄露。建议 smtp 自身返回通用文案,细节仅入日志。
|
||||
|
||||
---
|
||||
|
||||
## 八、已验证生效的安全机制(smtp_v2.py)
|
||||
|
||||
以下功能经初版验证与复审静态审查确认实现正确,予以背书:
|
||||
|
||||
1. ✅ 同邮箱 60s 发送冷却(冷却失败回滚正确,见 §五 P1-F1 注)
|
||||
2. ✅ 邮箱级限流(gateway 层双窗口:3次/小时 + 10次/天)
|
||||
3. ✅ 验证码一次性消费(校验通过即删,防重放)
|
||||
4. ✅ 连续失败 5 次自动作废(防 6 位码在线枚举)
|
||||
5. ✅ 10 分钟有效期 + 惰性过期清理
|
||||
6. ✅ 发送失败不计入冷却(防止网络错误误限制)—— *注意:冷却回滚正确,但配额/熔断回滚存在 P1-F1 缺陷*
|
||||
7. ✅ 全局熔断(可选)`SMTP_HOURLY_LIMIT` 默认 100 封/小时(配置生效)
|
||||
8. ✅ fail-closed:SMTP 异常时校验失败、发送失败
|
||||
9. ✅ gateway 边缘对 smtp 内部错误掩码(见 P3-F7)
|
||||
10. ✅ 配置缺失仅报 key 名不报 value(L127-132)
|
||||
|
||||
---
|
||||
|
||||
## 九、遗留项与接受的风险
|
||||
|
||||
| 编号 | 等级 | 说明 | 处置建议 |
|
||||
|------|------|------|---------|
|
||||
| P0-2 | P0 | pymsg 协议无认证,smtp 依赖 127.0.0.1 网络隔离 | 与全栈一致;建议纳入 `SECURITY_HARDENING_PLAN` 的内网 mTLS/共享密钥方案统一解决 |
|
||||
| P1-1 | P1 | 验证码存进程内存,服务重启即失效 | 单实例可接受;若需多实例/高可用应引入集中式存储(Redis 等)并配失效通知 |
|
||||
|
||||
---
|
||||
|
||||
## 十、测试覆盖盲区
|
||||
|
||||
`tests/blackbox/test_smtp.py` 仅覆盖:缺参、空 email、错误码、不存在域名正向响应。
|
||||
|
||||
**缺失的关键用例**(均未被覆盖,P1-F1 正处于盲区):
|
||||
|
||||
- 同邮箱连续成功发送的 60s 冷却行为(`retry_after` 返回)
|
||||
- 邮箱级 `3次/小时` / `10次/天` 限流触发
|
||||
- **SMTP 发送失败时配额/熔断是否被回滚**(直接命中 P1-F1 的回归测试)
|
||||
- 全局熔断 `SMTP_HOURLY_LIMIT` 触发与恢复
|
||||
- 连续 5 次错误校验后验证码作废(`invalidated`)
|
||||
|
||||
**建议**:补充以 `unittest.mock` 桩接 `smtplib.SMTP_SSL` 的单元测试,覆盖"发送失败→配额不变"这一回归断言,将 P1-F1 纳入防护网。
|
||||
|
||||
---
|
||||
|
||||
## 十一、修复路线图
|
||||
|
||||
### 立即(本周)
|
||||
- [x] 确认 smtp 服务运行的是 smtp_v2.py,旧版下线 ✅ 已确认(`_run.sh` 使用 `smtp_v2.py`,2026-08-04 删除旧版)
|
||||
- [x] 验证全局熔断配置生效 ✅ 已确认
|
||||
- [ ] **P1-F1**:重命名限流预占时间戳变量,修复回滚(提供代码见 §五)
|
||||
- [ ] **P2-F2**:从日志中移除验证码明文
|
||||
|
||||
### 短期
|
||||
- [ ] 验证全局熔断配置生效
|
||||
- [ ] **P2-F3**:`random` → `secrets`
|
||||
- [ ] **P3-F4**:`hmac.compare_digest` 比较验证码
|
||||
- [ ] 补充 §十 列出的回归测试用例
|
||||
|
||||
### 跟踪
|
||||
- [ ] **P0-2** / **P1-1**:纳入整体内网加固与高可用规划
|
||||
- [ ] 验证码存储考虑持久化(可选,当前内存实现适合单实例)
|
||||
Reference in new issue
Block a user