diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 0bb5d19ab..ef1bfd604 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** Go's standard `http.ListenAndServe` and unconfigured `http.Server` instances lack default timeouts for reading headers, reading bodies, and writing responses. **Learning:** These default settings leave the application vulnerable to resource exhaustion and Denial of Service (DoS) attacks, such as Slowloris, because malicious clients can slowly send data and tie up server connections indefinitely. **Prevention:** Always instantiate `http.Server` explicitly and set `ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout`, and (optionally) `IdleTimeout` to reasonable values based on the expected request sizes and latencies. + +## 2025-02-28 - [MEDIUM] Fix missing HTTP client timeouts in OAuth +**Vulnerability:** Go's standard `http.Post`, `http.PostForm`, `http.Get`, and the default `http.Client` do not have timeouts configured by default. +**Learning:** These defaults can leave the application vulnerable to resource exhaustion or indefinite hangs if the external service (like an OAuth provider) is slow, unresponsive, or experiencing an outage. +**Prevention:** Always instantiate `http.Client` explicitly with a sensible `Timeout` (e.g., `Timeout: 15 * time.Second`) before making outbound HTTP requests, instead of using the default package-level convenience functions. diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index d503d1721..be4f5528b 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -208,7 +208,8 @@ func RequestDeviceCode(cfg OAuthProviderConfig) (*DeviceCodeInfo, error) { "client_id": cfg.ClientID, }) - resp, err := http.Post( + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.Post( cfg.Issuer+"/api/accounts/deviceauth/usercode", "application/json", strings.NewReader(string(reqBody)), @@ -299,7 +300,8 @@ func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) { "client_id": cfg.ClientID, }) - resp, err := http.Post( + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.Post( cfg.Issuer+"/api/accounts/deviceauth/usercode", "application/json", strings.NewReader(string(reqBody)), @@ -358,7 +360,8 @@ func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*Au "user_code": userCode, }) - resp, err := http.Post( + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.Post( cfg.Issuer+"/api/accounts/deviceauth/token", "application/json", strings.NewReader(string(reqBody)), @@ -410,7 +413,8 @@ func RefreshAccessToken(cred *AuthCredential, cfg OAuthProviderConfig) (*AuthCre tokenURL = cfg.TokenURL } - resp, err := http.PostForm(tokenURL, data) + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.PostForm(tokenURL, data) if err != nil { return nil, fmt.Errorf("refreshing token: %w", err) } @@ -506,7 +510,8 @@ func ExchangeCodeForTokens(cfg OAuthProviderConfig, code, codeVerifier, redirect provider = "google-antigravity" } - resp, err := http.PostForm(tokenURL, data) + client := &http.Client{Timeout: 15 * time.Second} + resp, err := client.PostForm(tokenURL, data) if err != nil { return nil, fmt.Errorf("exchanging code for tokens: %w", err) }