Skip to content

fix(security): P0 加固 — CORS凭证 / 反代真实IP / 统一认证限流 / 服务token缓存收窄 - #93

Merged
Aqr-K merged 7 commits into
v3-pythonfrom
fix/p0-security
Jun 29, 2026
Merged

fix(security): P0 加固 — CORS凭证 / 反代真实IP / 统一认证限流 / 服务token缓存收窄#93
Aqr-K merged 7 commits into
v3-pythonfrom
fix/p0-security

Conversation

@Aqr-K

@Aqr-K Aqr-K commented Jun 28, 2026

Copy link
Copy Markdown
Owner

概述

修复架构审计 §6 标记的 P0 安全遗留项(4 项),均为安全加固、不破坏对外行为。基于当前 v3-python(含 #92),全量 1985 passed / 1 skipped / 0 failed

变更

1. CORS 凭证与通配源不可共存(factory.py

allow_origins=["*"](默认)+ allow_credentials=True 是误配——浏览器本就拒绝 *+credentials。
_cors_allow_credentials(origins):origins 含 *allow_credentials=False,仅配置具体源时才放行带凭证跨域。同源前端不受影响。

2. 反代真实客户端 IP / XFF 信任白名单(main.py + config.py

uvicorn 启用 proxy_headers=True + 新增 settings.FORWARDED_ALLOW_IPS(默认 127.0.0.1,仅信任本机)。
Docker+反代部署下,运维将其设为反代/网桥 IP 后,按可信代理白名单还原真实客户端 IP,使 (ip:username) 限流按真实 IP 生效——消除"所有客户端塌缩为反代 IP"导致的限流自伤 + 暴破绕过盲区。强制白名单避免不可信网络下伪造 XFF 绕过。

3. 统一认证限流器(core/auth_rate_limit.py + login.py + auth.py

/access-token/auth/flow/* 各持独立 KeyedWindowRateLimiter → 同 (ip,username) 暴破预算翻倍(10+10=20/60s)。
新增单一共享实例 auth_rate_limiter,三端点别名复用 → 统一 10/60s。保留模块级别名(_auth_rate_limiter / _auth_advance_rate_limiter)供端点代码引用与测试 monkeypatch,零测试改动

4. 服务 token 缓存 de-auth 收窄(security.py

__create_superuser_token_payload@cached TTL 600→60s + guard 增 is_active 校验。
超管账户被停用/降权后,API_TOKEN/API_KEY 仍被当作超管的 de-auth 窗口由 600s 收窄至 ≤60s,并在缓存刷新时拒绝非激活账户。

测试计划

  • 全量 pytest tests/1985 passed / 1 skipped / 0 failed
  • 新增 tests/test_p0_security_hardening.py:CORS 凭证逻辑 / 三端点共用同一限流器实例 / FORWARDED_ALLOW_IPS 安全默认
  • 既有限流 monkeypatch 测试(test_api_authorization / test_auth_security_regression / test_sso_flow_unified)不变通过

运维须知(BREAKING-ish 配置语义)

  • 反代部署:需设 FORWARDED_ALLOW_IPS=<反代/网桥 IP> 才能还原真实客户端 IP(否则限流按反代 IP,与此前行为一致)。
  • 跨域带凭证场景:需把具体来源写入 ALLOWED_HOSTS(不能再依赖 *+凭证)。

关联

🤖 Generated with Claude Code

Aqr-K and others added 4 commits June 28, 2026 11:14
修复审计 §6 P0 安全遗留项(均不破坏对外行为):

- CORS:allow_origins 含 `*` 时 `allow_credentials` 置 False(`*`+credentials 浏览器本就
  拒绝、服务端声明属误配)。抽 factory._cors_allow_credentials 可测。
- XFF/反代:uvicorn 启用 proxy_headers + 新增 settings.FORWARDED_ALLOW_IPS(默认仅信任本机),
  Docker+反代下据可信代理白名单还原真实客户端 IP,使 (ip:username) 限流按真实 IP 生效,
  消除『所有客户端塌缩为反代 IP』的限流自伤/绕过盲区。
- 认证限流统一:新增 app/core/auth_rate_limit.auth_rate_limiter 单一共享实例,
  /access-token 与 /auth/flow/{begin,advance} 别名复用同一桶,消除同 (ip,username) 暴破预算
  翻倍(原 10+10=20/60s → 统一 10/60s)。保留模块级别名供测试 monkeypatch。
- 服务 token 缓存:__create_superuser_token_payload 的 @cached TTL 600→60s + guard 增
  is_active 校验,将超管被停用/降权后 API_TOKEN/API_KEY 仍当超管的 de-auth 窗口由 600s 收窄至 ≤60s。

新增 tests/test_p0_security_hardening.py(CORS 凭证逻辑 / 限流器同一实例 / FORWARDED_ALLOW_IPS)。
全量 1985 passed / 1 skipped / 0 failed。
落实 docs/architecture-audit-v3python.md §6 P1 路线图剩余项(#32+#24+#60 分发内核统一已先期合入)。

- #37 插件 _plugins/_running_plugins 跨线程无锁:新增 _plugins_lock(RLock),start/stop/分身摘除
  改为锁内「原子整体替换」写入(读侧遍历的是替换前的完整字典,免锁安全);外部就地 .plugins.pop
  收口为 remove_plugin_class();start() 认证失败替换的 check-then-act 收进锁内消除 TOCTOU。
- #36 热重载只刷一半:reload_plugin 内聚重建调度服务/菜单命令/API 路由(惰性导入 register_plugin),
  绑定就绪后再广播 PluginReload;去除 API 端点与 agent 工具 reload_plugin_runtime 的冗余外层补刷。
- #44 DB 引擎 import 期副作用:WAL/journal_mode 改 @event.listens_for(connect) 按连接设置,异步注册于
  sync_engine;删除 import 期 engine.connect()/asyncio.run() 副作用与 print;监听器内异常仅告警放行,
  避免 PRAGMA 失败从监听器抛出导致连接池拒绝全部连接。
- #46 装饰器逐调用非原子:新增 atomic_session/async_atomic_session 上下文,配合会话 _atomic_managed
  标记令 @db_update/@async_db_update 在托管会话内不各自提交,多步写入收敛单事务统一提交/回滚;
  既有 db= 复用路径(无标记)逐字节不变。
- #15 分发吞异常无法区分错误/空:recognize_media/async_recognize_media 增可选 raise_exception,仅为真
  时才注入分发控制位透传(后端 **kwargs 吸收、on_error 据此重抛),默认路径不携带该 kwarg、行为不变。

对抗式复核(多 agent)确认并已修:start() check-then-act TOCTOU、WAL 监听器缺异常处理、reload 同步绑定
失败后仍发事件(改为绑定成功后再广播)。

测试:新增 tests/test_db_atomic_session.py(提交/回滚/默认兼容)。全量 1898 passed;既有 telegram f-string
收集错误(Python 3.11 vs 3.12)与 smb 内建存储缺可选依赖为预存在基线失败,与本改动无关(stash 后同样失败)。
fix(arch): P1 技术债 — 插件并发整体替换/热重载内聚绑定/会话原子化/识别严格模式/WAL连接级
…立成模块(审计 #61)

将 MediaInteractionChain(1461 行)从 app/chain/message.py 抽出为新模块
app/chain/media_interaction.py,类体逐字节不变(纯移动)。message.py 由 3369
行降至 1900 行(<2000 行上帝文件阈值),ruff F401 核验删除随类移出后不再使用的
17 个 import;经再导出保持 from app.chain.message import MediaInteractionChain
对外导入路径不变,并供 MessageChain 内部实例化。

tests/test_media_interaction.py:7 处 mock patch 目标从 app.chain.message 重定向
至 app.chain.media_interaction(DirectoryHelper/DownloadChain 协作者随类迁移,
mock 须打在名字被查找的模块)。

验证:全量 1970 passed/19 skipped(与基线一致);MediaInteractionChain 类体与
MessageChain 体均逐字节不变;ruff 净;3 维度对抗审查 0 新回归 —— 行为保持。

P2 其余项:#60 双轨已于 2cc5ccf6 完成;#16 拆 ChainBase(可行为保持但需重排
~1300 行核心基类、28 子类依赖,diff 大)按分析建议留独立 PR;#9 拆 Scheduler/
Monitor 与 #38 扩展点登记表存在无法行为保持的特例,本轮不做。
Aqr-K and others added 3 commits June 29, 2026 19:27
refactor(chain): P2 结构治理 — 拆分 message.py 上帝文件,MediaInteractionChain 独立成模块(审计 #61
按域将 ChainBase 的 88 个方法拆入 6 个 in-file Mixin 类(缓存/识别/搜索/下载转移/
通知/元数据分类),ChainBase 继承之;__init__ 与 12 个 name-mangling 分发内核方法
(__execute_*/__handle_*/__*_entries/__is_valid_empty/run_module/async_run_module)
保留本类(mangling 簇与调用方须同类,不可拆)。28 子类 + 市场插件零改动:公共 API、
from app.chain import ChainBase 导入路径、方法名/签名全不变。

选 in-file 而非分文件:测试有 ~21 处直接 patch chain 模块命名空间的协作者
(app.chain.MoviePilotServerHelper/MessageTemplateHelper),分文件会使这些 mock
注入点失效需逐一重定向;in-file 让模块级符号留原命名空间→零测试改动、零 patch 失效。

验证:88 方法体与重构前逐字节一致(纯移动);方法集经 name-mangling 改写后等价
(88=88,零丢失/新增);全量 1970 passed/19 skipped(=基线);ruff 净;无
__dict__/vars/__mro__ 内省依赖;对抗审查无新回归 —— 行为保持。
refactor(chain): P2 结构治理 — 拆 ChainBase 上帝基类为 6 域内聚 Mixin(审计 #16
@Aqr-K
Aqr-K merged commit 62eecb3 into v3-python Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant