Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2026-08-14 - Fix Potential Slowloris Attack Vulnerability (G112)
**Vulnerability:** Found `http.Server` implementations missing `ReadHeaderTimeout` in `internal/provideroauth/openrouter.go`, `internal/oauth/loopback.go`, and `internal/mcp/oauth.go`.
**Learning:** By default, Go's `http.Server` does not have a timeout for reading the request headers. This allows malicious clients to keep connections open indefinitely by sending header data very slowly, leading to resource exhaustion (Slowloris attack).
**Prevention:** Always set `ReadHeaderTimeout` when initializing an `http.Server` instance.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/charmbracelet/x/term v0.2.2
github.com/coder/websocket v1.8.15
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728
golang.org/x/image v0.44.0
golang.org/x/image v0.45.0
golang.org/x/sys v0.47.0
mvdan.cc/sh/v3 v3.13.1
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
Expand Down
1 change: 1 addition & 0 deletions internal/mcp/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ func Login(ctx context.Context, options LoginOptions) (StoredToken, error) {
}
resultChan := make(chan callbackResult, 1)
server := &http.Server{
ReadHeaderTimeout: 10 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/callback" {
http.NotFound(w, r)
Expand Down
5 changes: 4 additions & 1 deletion internal/oauth/loopback.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ func NewLoopbackListenerOnPort(state string, port int) (*LoopbackListener, error
state: state,
result: make(chan callbackResult, 1),
}
l.server = &http.Server{Handler: http.HandlerFunc(l.handle)}
l.server = &http.Server{
ReadHeaderTimeout: 10 * time.Second,
Handler: http.HandlerFunc(l.handle),
}
go func() { _ = l.server.Serve(ln) }()
return l, nil
}
Expand Down
36 changes: 19 additions & 17 deletions internal/provideroauth/openrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,28 @@ func OpenRouterLogin(ctx context.Context, opts OpenRouterOptions) (string, error

codeCh := make(chan string, 1)
errCh := make(chan error, 1)
server := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/callback" {
http.NotFound(w, r)
return
}
if code := strings.TrimSpace(r.URL.Query().Get("code")); code != "" {
_, _ = io.WriteString(w, "OpenRouter authorization complete. You may close this window.")
server := &http.Server{
ReadHeaderTimeout: 10 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/callback" {
http.NotFound(w, r)
return
}
if code := strings.TrimSpace(r.URL.Query().Get("code")); code != "" {
_, _ = io.WriteString(w, "OpenRouter authorization complete. You may close this window.")
select {
case codeCh <- code:
default:
}
return
}
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(w, "Authorization failed. You may close this window.")
select {
case codeCh <- code:
case errCh <- errors.New("provideroauth: callback missing authorization code"):
default:
}
return
}
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(w, "Authorization failed. You may close this window.")
select {
case errCh <- errors.New("provideroauth: callback missing authorization code"):
default:
}
})}
})}
go func() { _ = server.Serve(listener) }()
defer func() {
shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), time.Second)
Expand Down
Loading