[Bug] SafePath 误拦截 Roundcube/ACME 公共路由,普通用户必须先访问管理员入口

Author: zhoushun98Created Aug 22, 2026Updated Aug 28, 2026

Operating System

Linux

OS Version

Debian GNU/Linux 12

System Architecture

x86_64

Docker Version

Docker version 29.2.0, build 0b9d198

Docker Compose Version

Docker Compose version v5.0.2

Reproducible in Latest Version?

Yes, occurs in the latest stable release (billionmail/core:4.9.3)

Reproduction Steps

  1. 安装 BillionMail,并在 .env 中配置非空的 SafePath,例如 AdminSecretPath
  2. 使用全新的无痕窗口直接访问 https://mail.example.com/roundcube/roundcube/
  3. 页面返回 404 Not Found
  4. 在同一浏览器中先访问 https://mail.example.com/AdminSecretPath
  5. 再访问 /roundcube,此时返回 200,Roundcube 登录页可以正常打开。

Observed Behavior

  • Roundcube 是普通邮箱用户的公开 Webmail 登录入口,却依赖管理员 SafePath 设置的 safe_path_pass 会话标记。
  • 普通邮箱用户因此必须知道管理员安全入口;无痕窗口、不同浏览器或会话过期后都会再次遇到 404。
  • /.well-known/acme-challenge/* 也被同一检查提前拦截,内置 HTTP-01 申请/续期会收到 404。
  • 返回发生在 Roundcube 和 ACME handler 之前,因此与 Roundcube、PHP-FPM、证书或 DNS 本身无关。

Expected Behavior

  • /roundcube/roundcube/ 及其子路径应直接进入 Roundcube 自身的登录和会话机制。
  • 访问 Roundcube 不应设置或绕过管理员的 safe_path_pass;其他管理路径仍应返回 404。
  • /.well-known/acme-challenge/ 应允许匿名访问,以便 HTTP-01 验证。
  • 管理后台的其他路径仍必须先访问配置的 SafePath

Supplemental Information

根因位于全局 HookBeforeServe:只有精确命中的 excludesURIs 和静态文件被放行,其他动态路由全部要求 safe_path_pass

相关代码:

https://github.com/Billionmail/BillionMail/blob/161ffee629414857effe24c920b3246a912ee6e8/core/internal/cmd/cmd.go#L163-L205

最小修复示例:

go
_, isExcludedURI := excludesURIs[r.URL.Path]
isRoundcubeURI := r.URL.Path == "/roundcube" ||
    strings.HasPrefix(r.URL.Path, "/roundcube/")
isACMEChallengeURI := strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/")

if isExcludedURI || isRoundcubeURI || isACMEChallengeURI {
    return
}

本地基于官方 4.9.3 对应源码验证:

  • 全新会话访问 /roundcube:200
  • Roundcube 设置 cookie 后访问 /overview:仍为 404
  • 管理员 SafePath:仍正常工作
  • HTTPS/SMTP/IMAP 服务不受影响
  • gofmtgo vet ./... 与短测试通过

关联问题:#61、#338、#30。