eleven / ocho Public

server: ReadHeaderTimeout on the broker/home/push listeners + the signin loopback (#940)

Closes the class PR #903 started: the three sibling services' shared
serve() shape called bare http.ListenAndServe on the TCP branch — the
documented container-platform entrypoint — with no header timeout, and
the unix-socket branch likewise. All three now build one http.Server
with ReadHeaderTimeout: 10s (same as main.go's listeners) and route
both branches through it. The lchat signin loopback receiver gets the
same cap for uniformity (loopback-only + self-terminating, so low
severity there).

Found by the Aikido feed triage; verified against the full repo sweep —
every other listener (main.go, router, cloud, controld, trycmd) already
carries the timeout.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 409d94add38fb52914bee0d139354ca9d5c68310 parent 49a2290
4 files changed, +24 −7
  • broker/main.go +7 −2
  • eleven/signin.go +3 −1
  • home/main.go +7 −2
  • push/main.go +7 −2
diff --git a/broker/main.go b/broker/main.go
index cf4df48..3b224ba 100644
--- a/broker/main.go
+++ b/broker/main.go
@@ -158,8 +158,13 @@ func loadOAuthApps() map[string]*oauthApp {
// serve listens on addr: a "/"-prefixed addr is a unix socket (how the router
// fronts this service); otherwise a TCP address. Mirrors the push relay.
func serve(addr string, h http.Handler) error {
+ // ReadHeaderTimeout caps how long a client may dribble request headers
+ // (Slowloris): the TCP branch binds straight to public TCP on a container
+ // platform. Same 10s the messenger's own listeners use (main.go).
+ srv := &http.Server{Handler: h, ReadHeaderTimeout: 10 * time.Second}
if !strings.HasPrefix(addr, "/") {
- return http.ListenAndServe(addr, h)
+ srv.Addr = addr
+ return srv.ListenAndServe()
}
if c, err := net.DialTimeout("unix", addr, 200*time.Millisecond); err == nil {
c.Close()
@@ -176,7 +181,7 @@ func serve(addr string, h http.Handler) error {
if err := os.Chmod(addr, 0o600); err != nil {
return fmt.Errorf("chmod socket %s: %w", addr, err)
}
- return http.Serve(ln, h)
+ return srv.Serve(ln)
}
func (s *Server) handler() http.Handler {
diff --git a/eleven/signin.go b/eleven/signin.go
index 6f90bf1..109fed5 100644
--- a/eleven/signin.go
+++ b/eleven/signin.go
@@ -174,7 +174,9 @@ func startLoopback() (*loopback, error) {
default: // a second hit after success — already handled
}
})
- lb.srv = &http.Server{Handler: mux}
+ // Loopback-only and torn down after one hit, but cap header reads anyway —
+ // same posture as every other listener (main.go).
+ lb.srv = &http.Server{Handler: mux, ReadHeaderTimeout: 10 * time.Second}
go lb.srv.Serve(ln)
return lb, nil
}
diff --git a/home/main.go b/home/main.go
index 1157fd8..89466b6 100644
--- a/home/main.go
+++ b/home/main.go
@@ -150,8 +150,13 @@ func openStore(dbPath string) (vaultStore, string, error) {
// it clobbers only a stale socket (nothing listening) and locks it to 0600,
// mirroring the main binary's listen().
func serve(addr string, h http.Handler) error {
+ // ReadHeaderTimeout caps how long a client may dribble request headers
+ // (Slowloris): the TCP branch binds straight to public TCP on a container
+ // platform. Same 10s the messenger's own listeners use (main.go).
+ srv := &http.Server{Handler: h, ReadHeaderTimeout: 10 * time.Second}
if !strings.HasPrefix(addr, "/") {
- return http.ListenAndServe(addr, h)
+ srv.Addr = addr
+ return srv.ListenAndServe()
}
if c, err := net.DialTimeout("unix", addr, 200*time.Millisecond); err == nil {
c.Close()
@@ -168,7 +173,7 @@ func serve(addr string, h http.Handler) error {
if err := os.Chmod(addr, 0o600); err != nil {
return fmt.Errorf("chmod socket %s: %w", addr, err)
}
- return http.Serve(ln, h)
+ return srv.Serve(ln)
}
// newServer builds the server. stateSecret keys the stateless ceremony/session
diff --git a/push/main.go b/push/main.go
index 0382ca2..209b0bf 100644
--- a/push/main.go
+++ b/push/main.go
@@ -97,8 +97,13 @@ func main() {
// it clobbers only a stale socket (nothing listening) and locks it to 0600,
// mirroring the main binary's listen().
func serve(addr string, h http.Handler) error {
+ // ReadHeaderTimeout caps how long a client may dribble request headers
+ // (Slowloris): the TCP branch binds straight to public TCP on a container
+ // platform. Same 10s the messenger's own listeners use (main.go).
+ srv := &http.Server{Handler: h, ReadHeaderTimeout: 10 * time.Second}
if !strings.HasPrefix(addr, "/") {
- return http.ListenAndServe(addr, h)
+ srv.Addr = addr
+ return srv.ListenAndServe()
}
if c, err := net.DialTimeout("unix", addr, 200*time.Millisecond); err == nil {
c.Close()
@@ -115,7 +120,7 @@ func serve(addr string, h http.Handler) error {
if err := os.Chmod(addr, 0o600); err != nil {
return fmt.Errorf("chmod socket %s: %w", addr, err)
}
- return http.Serve(ln, h)
+ return srv.Serve(ln)
}
// handler wires the routes. A method so tests exercise the real router via