Skip to content

fix(security): Web Admin 登录限流、会话与 API 表面安全加固 - #307

Merged
3aKHP merged 4 commits into
devfrom
fix/web-admin-security
Oct 19, 2026
Merged

3aKHP merged 4 commits into
devfrom
fix/web-admin-security

Conversation

@3aKHP

@3aKHP 3aKHP commented Oct 19, 2026

Copy link
Copy Markdown
Owner

概述

Web Admin 安全加固专项,源自一轮后台全面排查中的安全专项发现。与前端大修 PR 拆分评审,两者文件零重叠。

commits:

  1. fix(security): login throttle and session hardening — XFF 解析改为从右往左取最右一个非内网跳(伪造请求头无法再绕过限流或定向封禁管理员);信任代理判定从前缀字符串改为 ipaddress 标准库(172. 一刀切收敛到 RFC1918 实际的 172.16.0.0/12,Cloudflare 的 172.64+ 不再被误信);登录失败/封禁记录增加容量上限与过期清扫;新增会话绝对寿命 WEB_ADMIN_SESSION_MAX_AGE_HOURS(默认 168h);会话 id 落库改存 SHA-256;前端退出登录失败时保留登录态并如实提示。
  2. fix(security): add security headers, hide API docs — 全站响应统一下发 CSP / X-Frame-Options / X-Content-Type-Options / Referrer-Policy;管理 API 不再暴露 /docs、/redoc、/openapi.json(LLM 配置的公开机器契约继续由 scripts/ci/export_llm_configuration_openapi.py 在进程内导出)。
  3. fix(security): tieba sync via POST, sanitize 5xx — 贴吧同步改为 POST(恢复"GET 无副作用"语义,前端改用 fetch + AbortController 消费进度流并在离开页面时主动断开);贴吧 peek 与词云渲染的 5xx 不再回显内部异常文本;贴吧帖链只接受 http/https scheme。

升级影响

  • 所有存量登录态失效,升级后需重新登录一次(会话 id 哈希化 + 绝对寿命)。
  • 使用 nginx 反代的部署:建议把 X-Forwarded-For 改为覆盖语义(proxy_set_header X-Forwarded-For $remote_addr;)并按 docs/admin/web-admin.md 补充 limit_req。旧文档的追加语义在新代码下仍能正确解析(从右往左取值),不强制立刻改。

验证

  • .venv/bin/ruff check . 全过;pytest tests/unit 2923 全过(含新增 test_client_ip.py、test_auth_security.py、test_app_security.py、test_tieba_sync_route.py 与 crawler 用例)。
  • 本分支独立前端验证:type-check 零错误、node --test 56/56、vite build 成功(旧工具链基线)。
  • 定级门槛 scripts/check/deep-cr-trigger.sh dev:trigger=true(persistence + web-admin,24 files / 707 行),按仓库流程组织 Deep-CR 与独立 CR,结论汇总后请求人工合并。

CHANGELOG 草稿

修复

  • 登录限流与封禁的客户端 IP 识别改为取 X-Forwarded-For 链最右一个非内网跳,伪造请求头无法再绕过限流或定向封禁管理员;172. 前缀信任范围收敛到 RFC1918 实际的 172.16.0.0/12
  • 登录限流的失败/封禁记录增加容量上限与过期清扫,伪造来源无法再让 Web 进程内存无界增长
  • 管理 API 的 /docs、/redoc、/openapi.json 端点不再对外暴露;LLM 配置的公开机器契约继续由 CI 脚本在进程内导出(docs/dev/llm-configuration.openapi.json)
  • Web Admin 全部响应(静态页面与 API)统一下发安全响应头:CSP、X-Frame-Options、X-Content-Type-Options、Referrer-Policy
  • 5xx 错误响应不再回显内部异常文本(可能含服务器路径),统一为通用提示,真实异常进服务日志
  • 贴吧"立即同步"改为 POST 触发(GET 不再可用,恢复"GET 无副作用"语义),实时进度展示不变;关闭贴吧页面时会主动断开同步进度流
  • 退出登录失败时保留登录态并提示"会话仍有效",不再在本地误显示为已退出
  • 贴吧帖链只接受 http/https 链接,上游返回的异常 scheme 一律回退为帖子 canonical 地址

变更

  • 管理会话增加绝对寿命(WEB_ADMIN_SESSION_MAX_AGE_HOURS,默认 168 小时):自创建起超过该时长必须重新登录,滑动续期不能突破该上限;会话 id 落库改存 SHA-256 哈希。升级后所有存量登录态失效,需重新登录一次
  • 反代推荐配置的 X-Forwarded-For 改为覆盖语义(proxy_set_header X-Forwarded-For $remote_addr;),并补充 nginx limit_req 限流建议,详见 docs/admin/web-admin.md

3aKHP added 3 commits October 20, 2026 03:53
- Resolve client IP by walking X-Forwarded-For right-to-left and taking the rightmost untrusted hop; client-supplied XFF values can no longer bypass login throttling or pin bans on arbitrary IPs
- Replace prefix-string trusted-proxy check with ipaddress is_private/is_loopback so public ranges like 172.64.0.0/10 are no longer trusted (RFC1918 is 172.16.0.0/12)
- Cap the login failure/ban trackers and sweep expired entries so forged source IPs cannot grow web process memory unboundedly
- Add absolute session lifetime WEB_ADMIN_SESSION_MAX_AGE_HOURS (default 168h): sliding renewal can no longer extend a session past creation + max age; existing sessions are invalidated on upgrade
- Store only the SHA-256 hash of session ids in web_admin_sessions.db; plaintext ids stay in the browser cookie
- Keep the frontend authenticated state when the logout request fails, and tell the admin the session is still valid instead of showing a logged-out UI
- docs/admin/web-admin.md: recommend overwrite semantics for X-Forwarded-For (proxy_set_header X-Forwarded-For $remote_addr) and add an nginx limit_req example for /ops/
- Serve CSP, X-Frame-Options, X-Content-Type-Options and Referrer-Policy on every Web Admin response (static pages and API)
- Stop exposing /docs, /redoc and /openapi.json on the admin API; the public machine contract for llm-configuration keeps being exported in-process by scripts/ci/export_llm_configuration_openapi.py
- Tieba sync has real side effects (external fetching, pool rewrite), so it is now triggered with POST instead of GET, restoring the GET-has-no-side-effects assumption behind the same-origin check exemption; the frontend consumes the progress stream with fetch + AbortController (EventSource only supports GET) and aborts it when the page scope is disposed
- Stop echoing internal exception text in 5xx responses of tieba peek and wordcloud rendering (may contain server paths); clients get a generic message while the real exception goes to the service log
- Tieba thread links accept only http/https; any other scheme returned upstream falls back to the canonical thread URL
- Cap both login trackers at 4096 entries with oldest-key eviction: the fails table previously stopped tracking new sources at capacity (a throttling vacuum for brute force), and the bans table had no cap at all; comment now matches behavior
- Treat a corrupted session created_at as expired (delete + 401) instead of fail-open
- Log tieba peek 502 failures with logger.exception for traceback parity with the wordcloud routes
- Drop the dead app.openapi enrich monkeypatch: no HTTP entry remains since the docs endpoints were hidden, and the in-process export path is unaffected
- docs: configuration.md gains the WEB_ADMIN_SESSION_MAX_AGE_HOURS row; web-admin.md and the client_ip docstring now state the trusted-proxy assumption (a public front proxy such as a CDN/LB makes the limiter key the proxy egress; never bind the admin directly to an untrusted L2 network)
@3aKHP

3aKHP commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

评审汇合(Deep-CR 五透镜 + 独立 Tier-1 CR + 交叉复核)后的修复轮,新 head:8be8286

七轨评审结论:无 Blocking。交叉复核按规则保留置信度 ≥80 且确属本变更的发现,本 PR 侧已修复:

  • 限流器收口(三轨命中):_login_fails 满载时由拒收新键改为逐出最旧键(消除满载后的限流真空),_login_bans 补上同额容量上限,注释改为与实现一致
  • 会话 created_at 解析失败改为 fail-closed(删除会话 + 401)
  • tieba peek 502 改 logger.exception,与 wordcloud 路由一致保留堆栈
  • 删除 app.py 中已无 HTTP 入口的 enrich_openapi 猴补丁(进程内导出路径不受影响)
  • docs/admin/configuration.md 补收 WEB_ADMIN_SESSION_MAX_AGE_HOURS 并同步 self-docs 镜像(顺带修复了首轮 CI 的 self-docs 同步检查失败)
  • web-admin.md 与 client_ip.py docstring 声明可信代理假设:存在公网前置代理(CDN/LB)时限流键为出口地址、需在最外层限流;不要把后台直接绑定到不信任的二层网络

验证:ruff 全过;pytest 2925 全过(含新增的逐出/上限/损坏行用例)。

记录在案的取舍:多级代理链取跳的算法语义维持现状(仅文档声明边界,不引入可配置可信代理列表)。

@3aKHP
3aKHP merged commit e8c3f21 into dev Oct 19, 2026
4 checks passed
@3aKHP
3aKHP deleted the fix/web-admin-security branch October 19, 2026 21:15
3aKHP added a commit that referenced this pull request Oct 19, 2026
- Web Admin 安全加固(#307)与前端大修(#309)集成后的测试部署批次
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