编号:L-04 严重级:high 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 A-01、P-08 依赖:无 被依赖:无
两份审查结论一致:TLS 握手后内层连接又被 peekFirstByte 包成 bufferedConn 交给 http.Server,net/http 只在连接实现了 ConnectionState() 时才填 r.TLS,所以直连 TLS 时 httpx.IsHTTPS 恒为 false,setSessionCookie 不加 Secure(serve 也没设 SecureCookies)。按 DEVELOPMENT 11.3 推荐的"NixMsg 直接终止 TLS"部署时,浏览器会把管理员 Cookie 带到同主机的明文请求上。
peekFirstByte
bufferedConn
http.Server
ConnectionState()
r.TLS
httpx.IsHTTPS
setSessionCookie
SecureCookies
统一方案采用审查 P-08 的做法(改动最小,覆盖所有依赖 r.TLS 的代码):
tlsBufferedConn{*bufferedConn; tc *tls.Conn}
tc.ConnectionState()
allow_plaintext=false
admin.Deps.SecureCookies
Strict-Transport-Security
审查 A-01 提出的 ConnContext + 回填方案作为备选,不同时实现。严重级取 high:泄漏的是 12 小时有效的管理员会话。
internal/listener/conn.go、server.go;cmd/nixmsg/serve.go(admin.Deps 的 SecureCookies 一行)。
internal/listener/conn.go
server.go
cmd/nixmsg/serve.go
admin.Deps
classify
r.TLS != nil
httptest.NewTLSServer
*tls.Conn
以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。
nixmsg_admin
Secure
http://主机/
allow_plaintext: false
func (h *Handler) setSessionCookie(w http.ResponseWriter, r *http.Request, value string, maxAge int) { secure := h.forceSec || httpx.IsHTTPS(r, h.trusted) http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: value, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: secure, MaxAge: maxAge, })
tlsConn := tls.Server(c, s.certs.TLSConfig()) if err := tlsConn.Handshake(); err != nil { return KindClosed, nil, err } return s.classify(tlsConn, isAdmin, true) } // ... if b >= 'A' && b <= 'Z' { return KindHTTP, c, nil }
*bufferedConn
internal/listener/conn.go:22-42
internal/httpx/clientip.go:41-45
cmd/nixmsg/serve.go:177-226
Deps.SecureCookies
admin_test.go:379-400
IsHTTPS
listener.Server.Start
clientSrv
adminSrv
ConnContext
r.TLS == nil
ProxySet.IsHTTPS
serve.go
SecureCookies: true
internal/listener/server.go
allow_plaintext: true
HttpOnly
SameSite=Lax
Request.TLS
listener/server.go:298-302
conn.go:12-27
httpx/clientip.go:42-45
admin/login.go:12-22
net/http/server.go:1956-1962
2022-2027
HandshakeContext
复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。
4059a15
已合入 origin/main 0c9b459。落地提交 7c926a0 fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#23)。
0c9b459
7c926a0
No dependencies set.
The note is not visible to the blocked user.
编号:L-04 严重级:high 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 A-01、P-08
依赖:无 被依赖:无
结论与统一方案
两份审查结论一致:TLS 握手后内层连接又被
peekFirstByte包成bufferedConn交给http.Server,net/http 只在连接实现了ConnectionState()时才填r.TLS,所以直连 TLS 时httpx.IsHTTPS恒为 false,setSessionCookie不加 Secure(serve 也没设SecureCookies)。按 DEVELOPMENT 11.3 推荐的"NixMsg 直接终止 TLS"部署时,浏览器会把管理员 Cookie 带到同主机的明文请求上。统一方案采用审查 P-08 的做法(改动最小,覆盖所有依赖
r.TLS的代码):tlsBufferedConn{*bufferedConn; tc *tls.Conn},实现ConnectionState()返回tc.ConnectionState()。明文连接继续用原来的bufferedConn,不能给它加这个方法,否则明文请求也会被当成 TLS。allow_plaintext=false时,serve 给admin.Deps.SecureCookies传 true。Strict-Transport-Security(只在 TLS 请求上)。审查 A-01 提出的 ConnContext + 回填方案作为备选,不同时实现。严重级取 high:泄漏的是 12 小时有效的管理员会话。
改动文件
internal/listener/conn.go、server.go;cmd/nixmsg/serve.go(admin.Deps的 SecureCookies 一行)。与其他问题的交互 / 冲突说明
classify,监听线内顺序合入。admin.Deps也会被 H-02(审计 logger)修改,合并时保留两处。验收与测试
r.TLS != nil,明文请求断言为 nil。httptest.NewTLSServer用的是原生*tls.Conn,测不出这个问题。问题明细(各区审查原文,证据含文件与行号)
[A-01] 直连 TLS 时后台 Cookie 不带 Secure
nixmsg_admin的 Set-Cookie 没有Secure。这正是 DEVELOPMENT 11.3 要求的生产形态(明确说不要用 OpenResty 终止 TLS)。http://主机/。allow_plaintext: false时服务端读到首字节就断开,请求头也已经发出去了。classify里的peekFirstByte把*tls.Conn包成*bufferedConn(internal/listener/conn.go:22-42)。net/http 只在连接本身是*tls.Conn时才填r.TLS,所以httpx.IsHTTPS(internal/httpx/clientip.go:41-45,只看r.TLS != nil)在直连 TLS 下恒为 false。cmd/nixmsg/serve.go:177-226没有设置Deps.SecureCookies。admin_test.go:379-400只断言 Cookie 存在,不检查任何属性。IsHTTPS的注释写明要识别直连 TLS,只是在本项目的监听器下这条路径永远不成立。listener.Server.Start给clientSrv、adminSrv设ConnContext:连接是*bufferedConn且内层为*tls.Conn时,把ConnectionState()存进 context。r.TLS == nil时从 context 回填。这样所有依赖r.TLS的代码(httpx.IsHTTPS、ProxySet.IsHTTPS)一起恢复正常。allow_plaintext=false时,serve.go传SecureCookies: true。internal/listener/server.go(N 线)、cmd/nixmsg/serve.go(总控,兜底可选)、测试。allow_plaintext: true时明文请求仍不带 Secure,这是正确的,否则浏览器会拒收。WS 路径如果读r.TLS,行为会变成正确的。IsHTTPS应为 true,明文应为 false。Secure、HttpOnly、SameSite=Lax。httptest.NewTLSServer用的是原生*tls.Conn,测不出这个问题。[P-08] 经 TLS 的请求 r.TLS 为空,直接用 HTTPS 访问时后台 Cookie 不带 Secure
*tls.Conn包进bufferedConn交给 http.Server。这个包装只实现了 net.Conn,没有ConnectionState()方法。ConnectionState()时才设置Request.TLS,所以所有经 NixMsg 自己终止 TLS 的请求都是r.TLS == nil。httpx.IsHTTPS返回 false,setSessionCookie不加 Secure(serve 也没设SecureCookies)。listener/server.go:298-302;conn.go:12-27;httpx/clientip.go:42-45;admin/login.go:12-22;Go 1.27net/http/server.go:1956-1962、2022-2027。tlsBufferedConn{*bufferedConn; tc *tls.Conn},实现ConnectionState(),直接返回tc.ConnectionState()。明文连接继续用原来的bufferedConn;不能给它加这个方法,否则明文请求也会被当成 TLS。internal/listener/conn.go、server.goHandshakeContext方法,不会重复握手;没有协商 ALPN,仍走 HTTP/1.1。r.TLS != nil,明文请求断言为 nil;经 TLS 登录后台,Set-Cookie 带 Secure。复审基线:main
4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。已合入 origin/main
0c9b459。落地提交7c926a0fix: 修复 TLS ConnectionState、SPA 回退、健康检查与监听小问题 (#23)。