@@ -28,21 +28,24 @@ import (
2828)
2929
3030var (
31- serverURL string
32- clientID string
33- tokenFile string
34- tokenStoreMode string
35- flagServerURL * string
36- flagClientID * string
37- flagTokenFile * string
38- flagTokenStore * string
39- configInitialized bool
40- retryClient * retry.Client
41- tokenStore credstore.Store [credstore.Token ]
31+ serverURL string
32+ clientID string
33+ tokenFile string
34+ tokenStoreMode string
35+ flagServerURL * string
36+ flagClientID * string
37+ flagTokenFile * string
38+ flagTokenStore * string
39+ configOnce sync. Once
40+ retryClient * retry.Client
41+ tokenStore credstore.Store [credstore.Token ]
4242)
4343
4444const defaultKeyringService = "authgate-device-cli"
4545
46+ // maxResponseBodySize limits HTTP response body reads to prevent memory exhaustion (DoS).
47+ const maxResponseBodySize = 1 << 20 // 1 MB
48+
4649// Timeout configuration for different operations
4750const (
4851 deviceCodeRequestTimeout = 10 * time .Second
@@ -107,11 +110,12 @@ func init() {
107110// initConfig parses flags and initializes configuration
108111// Separated from init() to avoid conflicts with test flag parsing
109112func initConfig () {
110- if configInitialized {
111- return
112- }
113- configInitialized = true
113+ configOnce . Do ( func () {
114+ doInitConfig ()
115+ })
116+ }
114117
118+ func doInitConfig () {
115119 flag .Parse ()
116120
117121 // Priority: flag > env > default
@@ -438,7 +442,7 @@ func requestDeviceCode(ctx context.Context) (*oauth2.DeviceAuthResponse, error)
438442 }
439443 defer resp .Body .Close ()
440444
441- body , err := io .ReadAll (resp .Body )
445+ body , err := io .ReadAll (io . LimitReader ( resp .Body , maxResponseBodySize ) )
442446 if err != nil {
443447 return nil , fmt .Errorf ("failed to read response: %w" , err )
444448 }
@@ -638,7 +642,7 @@ func exchangeDeviceCode(
638642 }
639643 defer resp .Body .Close ()
640644
641- body , err := io .ReadAll (resp .Body )
645+ body , err := io .ReadAll (io . LimitReader ( resp .Body , maxResponseBodySize ) )
642646 if err != nil {
643647 return nil , fmt .Errorf ("failed to read response: %w" , err )
644648 }
@@ -696,7 +700,7 @@ func verifyToken(ctx context.Context, accessToken string, d tui.Displayer) error
696700 }
697701 defer resp .Body .Close ()
698702
699- body , err := io .ReadAll (resp .Body )
703+ body , err := io .ReadAll (io . LimitReader ( resp .Body , maxResponseBodySize ) )
700704 if err != nil {
701705 return fmt .Errorf ("failed to read response: %w" , err )
702706 }
@@ -746,7 +750,7 @@ func refreshAccessToken(
746750 }
747751 defer resp .Body .Close ()
748752
749- body , err := io .ReadAll (resp .Body )
753+ body , err := io .ReadAll (io . LimitReader ( resp .Body , maxResponseBodySize ) )
750754 if err != nil {
751755 return credstore.Token {}, fmt .Errorf ("failed to read response: %w" , err )
752756 }
@@ -871,7 +875,7 @@ func makeAPICallWithAutoRefresh(
871875 defer resp .Body .Close ()
872876 }
873877
874- body , err := io .ReadAll (resp .Body )
878+ body , err := io .ReadAll (io . LimitReader ( resp .Body , maxResponseBodySize ) )
875879 if err != nil {
876880 return fmt .Errorf ("failed to read response: %w" , err )
877881 }
0 commit comments