server: ReadHeaderTimeout on the instance + tunnel-gateway listeners (#903)
Slowloris hardening (Aikido SAST G112): the router, cloud, and controld listeners already cap header reads at 10s; the instance server (which a standalone self-host binds straight to public TCP) and the tunnel gateway missed it. ReadHeaderTimeout only bounds header parsing, so WebSockets and other long-lived connections are unaffected.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2 files changed,
+5
−2
main.go+4 −1trycmd.go+1 −1
diff --git a/main.go b/main.go| index d90687e..97470c4 100644 |
| --- a/main.go |
| +++ b/main.go |
| @@ -326,7 +326,10 @@ func main() { |
| } |
| log.Printf("lchat listening on %s (db: %s)", where, *dbPath) |
| - srv := &http.Server{Handler: s.handler()} |
| + // ReadHeaderTimeout caps how long a client may dribble request headers |
| + // (Slowloris): standalone self-hosts bind this listener straight to public |
| + // TCP. Same 10s the router/cloud/controld listeners use. |
| + srv := &http.Server{Handler: s.handler(), ReadHeaderTimeout: 10 * time.Second} |
| go func() { |
| if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { |
| log.Fatalf("serve: %v", err) |
diff --git a/trycmd.go b/trycmd.go| index d24b274..e8bd45d 100644 |
| --- a/trycmd.go |
| +++ b/trycmd.go |
| @@ -254,7 +254,7 @@ func runTunnelCmd(args []string) { |
| } |
| _ = os.Chmod(*socket, 0o660) |
| fmt.Printf("tunnel gateway: %s (control %s, minting try-*.%s)\n", *socket, *host, *domain) |
| - srv := &http.Server{Handler: gw} |
| + srv := &http.Server{Handler: gw, ReadHeaderTimeout: 10 * time.Second} |
| if err := srv.Serve(ln); err != nil { |
| die("tunnel serve: %v", err) |
| } |