Skip to content

Commit dbdd3a0

Browse files
appleboyclaude
andcommitted
fix(main): bound HTTP response reads and use sync.Once for config init
- Limit all HTTP response body reads to 1 MB using io.LimitReader to prevent memory exhaustion - Replace non-thread-safe configInitialized bool with sync.Once for safe concurrent initialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b67022b commit dbdd3a0

1 file changed

Lines changed: 24 additions & 20 deletions

File tree

main.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,24 @@ import (
2828
)
2929

3030
var (
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

4444
const 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
4750
const (
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
109112
func 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

Comments
 (0)