Skip to content

Commit 6b7b7cf

Browse files
committed
fix: harden JWT bearer exchange against timeouts, HTTP downgrade, and large responses
- Replace http.DefaultClient with a dedicated client with 30s timeout - Reject non-HTTPS token URIs to prevent JWT assertion leakage - Cap response body reads at 1 MB to prevent memory exhaustion
1 parent 03c7ce5 commit 6b7b7cf

File tree

1 file changed

+14
-2
lines changed

1 file changed

+14
-2
lines changed

internal/authutil/machineaccount.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,22 @@ func MintJWT(clientID, privateKeyID, privateKeyPEM, tokenURI string) (string, er
108108
return signed, nil
109109
}
110110

111+
// tokenHTTPClient is used for all JWT bearer token exchanges.
112+
// A dedicated client with a timeout prevents indefinite hangs on slow endpoints.
113+
var tokenHTTPClient = &http.Client{Timeout: 30 * time.Second}
114+
111115
// ExchangeJWT POSTs a signed JWT to tokenURI using the jwt-bearer grant and
112116
// returns the resulting oauth2.Token. The token will have no RefreshToken.
113117
// If scope is empty, "openid profile email" is used as the default.
114118
func ExchangeJWT(ctx context.Context, tokenURI, signedJWT, scope string) (*oauth2.Token, error) {
119+
u, err := url.Parse(tokenURI)
120+
if err != nil {
121+
return nil, fmt.Errorf("failed to parse token URI: %w", err)
122+
}
123+
if u.Scheme != "https" {
124+
return nil, fmt.Errorf("token_uri must use HTTPS, got %q", u.Scheme)
125+
}
126+
115127
if scope == "" {
116128
scope = "openid profile email"
117129
}
@@ -126,13 +138,13 @@ func ExchangeJWT(ctx context.Context, tokenURI, signedJWT, scope string) (*oauth
126138
}
127139
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
128140

129-
resp, err := http.DefaultClient.Do(req)
141+
resp, err := tokenHTTPClient.Do(req)
130142
if err != nil {
131143
return nil, fmt.Errorf("JWT bearer token request failed: %w", err)
132144
}
133145
defer resp.Body.Close()
134146

135-
body, err := io.ReadAll(resp.Body)
147+
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) // 1 MB cap
136148
if err != nil {
137149
return nil, fmt.Errorf("failed to read JWT bearer response: %w", err)
138150
}

0 commit comments

Comments
 (0)