Skip to content

fobe 0.1.13

Choose a tag to compare

@fonlan fonlan released this 20 Sep 09:23
· 10 commits to main since this release

fobe 0.1.13

安全:一轮认证 / 请求 / 出站 / 部署加固(本次发布主体)

运维侧需要留意的变化逐条列出

  • XFF 改为「从右往左取第一个不在 FOBE_TRUSTED_PROXIES 内的地址」:旧版取最左,配 nginx 的 $proxy_add_x_forwarded_for(追加)等于让每个客户端自选来源 IP,伪造私网地址还能落进 §4.3 的「永不加黑」豁免。README 的 nginx 示例改用 $remote_addr(替换式)。服务端的右起取址让追加式写法也不再能被伪造,但替换式才是推荐配置。
  • /api/login 前置一道与来源身份无关的全局闸(突发 30 / 每秒 10 / 并发 4):黑名单按 IP 计数而身份可以无限多,每次校验固定吃 64 MiB,内存上界必须不可伪造。
  • 失败计数随封禁一起过期:成功登录无条件删行,RecordLoginFail 发现存量行已过期先归零。此前一次手滑之后「每次再错一次就重封 30 分钟」,且残留行在面板上不可见。
  • 会话新增 30 天服务端绝对有效期security.SessionExpired + 清理任务);GET /api/sessions 只回 id_short 指纹、不再回显 cookie 值;改密码 / revoke-all 会关闭对应的活动 WS(WS 只在握手时认证一次)。
  • must_change 在服务端强制:除 /api/me/api/password/api/logout 外一律 409。
  • 请求体上限:默认 8 MiB,/api/import 与 MMDB 上传 64 MiB;http.ServerReadTimeout / IdleTimeout(刻意不设 WriteTimeout)。
  • /sub/{token} 访问日志脱敏/api/health 去掉 initialized,登录失败不再区分 not_initialized
  • Sec-Fetch-Site 跨站拒绝,只作用于变更类方法与两个有副作用的 GET(/api/export/api/nodes/{id}/forwards?live=1)。
  • 安全响应头:CSP + X-Frame-Options + nosniff + Referrer-Policy + Permissions-Policy,https 时加 HSTS;Cookie 的 Secure 由「请求自身是 https」或「server.public_url 是 https」推导。
  • 出站守卫security.CheckOutboundHost / GuardClient):AI provider base_url 与通知 webhook 拒绝 link-local(含云元数据 169.254.169.254)/未指定/组播,放行 loopback 与 RFC1918,并逐跳复检重定向;geoip.url 补入同一守卫(写侧与下载侧,302 劫持引下载器进内网被拒)。上游响应体不再进入面板可见的错误文本。
  • 主密钥解不开的密钥不再静默失效:启动记 error + secret_unreadable 审计,GET /api/settingsunreadable_secrets,测试按钮回 notify_secret_unreadable,通知页顶部列出这些键。
  • cmd_result 的 WHERE 补 node_id 与状态条件(曾可由任一探针终结他节点的排队命令);构建 VERSION 过字符集校验(agentupdate.SafeVersionPart,启动即拒非法值);sing-box 产物下载加解压总量上限;终端 WS 每节点上限 4。
  • 容器改以 fobe(10001) 提供服务:入口脚本只为接管 /data 属主短暂以 root 运行后 su-exec 降权;compose 加 no-new-privileges + cap_drop: ALL 并只回补入口所需能力。升级提示:首次启动会把 /data 卷内文件属主改成 uid 10001;用仓库 compose 的请一并 git pull 拿新的 security_opt/cap_drop

新增:面板登录失败告警

  • 新增 security 事件组(设置 → 通知里可开关):登录校验失败推 login_failed(ip / count / max_fails),跨过 3 次阈值推 login_blacklisted(ip / count / block_seconds);受保护网段的失败也推送但不计数(payload protected:true),一次失败只产生一条告警。
  • 去重是全局 1 小时而非按 IP:/api/login 无需认证,按来源去重等于把消息条数交给换 IP 的攻击者;逐条来源地址仍在 audit_logs
  • 新增 TestEveryEventGroupSwitchIsWritable 钉住「每个事件组开关都可写」——此前 allowedKeys 手写,漏加新组会渲染出 PUT 被拒、GET 也不返回的开关。

修复

  • 节点设置保存后「已保存」不可见:成功提示被表单的 key 重挂载清掉(key 绑节点数据是有意设计),现在提示上移到父级卡片标题行,跟随基础设置页 3 秒自动清除,并用 ref 存定时器句柄(连续保存不会被旧定时器提前清掉)。
  • 通知测试的 i18n 键:页面调 feishu_test_secret_unreadable,字典里加的却是零引用的 err_notify_secret_unreadable——tsc 全绿,运行时把键名原样显示给用户。已改名对齐(zh/en 同步)。
  • Debug 访问日志也脱敏:此前只有 5xx 分支走 logPath(),日志级别开到 Debug 时 /sub/<token> 仍明文入库。
  • 出站守卫补拦一般组播:原条件只挡两个组播子类,实测 239.1.2.3ff05::1 被放行,与函数注释和 design §12.5 不符(只更严不更松)。

重构(行为不变)

  • internal/agent/singbox.go(1526 行)按 §9 缝线拆为 singbox_local / singbox_firewall / singbox_install / singbox_gates / singbox_proc 五个文件;删死代码 compareVersions / versionPart / atoiSafe / runQuiet / service.InstallAgentfetchChecksum 委托同包 fetchExpectedSHA256,同一二进制里不再有两份 sidecar 解析器。
  • httpapi 新增 writeStoreErr / decodeReq helper(替换 34 + 30 处样板,语义非统一的 22 处刻意保留);store 的 scan 族推广到 ai_models / alerts / commands / templates;validateSettingValue 成为 PUT /api/settings 与 §17 导入共用的单值校验源——导入侧因此变严(坏 theme / webhook URL 现在会被丢弃)。
  • 前端新增 useAsyncAction,收敛 EditServer / SettingsAI / Settings 三页 29 处 busy/err/msg 样板;新增 ErrorStatechartShared.tsx 共享件;删 26 个零引用 i18n 键与若干死代码。

无 schema / 无 wire 变更

  • 数据库 schema 未动(仍 16),internal/protocol 未动,无新增必填环境变量。
  • VERSION = 0.1.13,镜像仅 linux/amd64。