-
Notifications
You must be signed in to change notification settings - Fork 0
fix(main): address medium-severity security findings #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os/signal" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "sync" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "syscall" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,18 +29,18 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serverURL string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientID string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientSecret string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirectURI string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callbackPort int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenFile string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenStoreMode string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenStore credstore.Store[credstore.Token] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configInitialized bool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retryClient *retry.Client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configWarnings []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serverURL string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientID string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientSecret string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| redirectURI string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callbackPort int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenFile string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenStoreMode string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenStore credstore.Store[credstore.Token] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configOnce sync.Once | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retryClient *retry.Client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configWarnings []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flagServerURL *string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flagClientID *string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -55,6 +56,7 @@ const ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenExchangeTimeout = 10 * time.Second | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tokenVerificationTimeout = 10 * time.Second | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refreshTokenTimeout = 10 * time.Second | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxResponseSize = 1 << 20 // 1 MB | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func init() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -96,16 +98,21 @@ func init() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // initConfig parses flags and initializes all configuration. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func initConfig() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if configInitialized { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configInitialized = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configOnce.Do(doInitConfig) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func doInitConfig() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.Parse() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serverURL = getConfig(*flagServerURL, "SERVER_URL", "http://localhost:8080") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientID = getConfig(*flagClientID, "CLIENT_ID", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientSecret = getConfig(*flagClientSecret, "CLIENT_SECRET", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if *flagClientSecret != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configWarnings = append(configWarnings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Client secret passed via command-line flag. "+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "This may be visible in process listings. "+ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Consider using CLIENT_SECRET env var or .env file instead.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+104
to
+115
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func doInitConfig() { | |
| flag.Parse() | |
| serverURL = getConfig(*flagServerURL, "SERVER_URL", "http://localhost:8080") | |
| clientID = getConfig(*flagClientID, "CLIENT_ID", "") | |
| clientSecret = getConfig(*flagClientSecret, "CLIENT_SECRET", "") | |
| if *flagClientSecret != "" { | |
| configWarnings = append(configWarnings, | |
| "Client secret passed via command-line flag. "+ | |
| "This may be visible in process listings. "+ | |
| "Consider using CLIENT_SECRET env var or .env file instead.") | |
| } | |
| // computeConfigWarningsFromFlags returns configuration warnings derived solely | |
| // from command-line flag values. This is separated from doInitConfig so that | |
| // the warning behavior can be unit tested without relying on global flag state. | |
| func computeConfigWarningsFromFlags(clientSecretFlag string) []string { | |
| var warnings []string | |
| if clientSecretFlag != "" { | |
| warnings = append(warnings, | |
| "Client secret passed via command-line flag. "+ | |
| "This may be visible in process listings. "+ | |
| "Consider using CLIENT_SECRET env var or .env file instead.") | |
| } | |
| return warnings | |
| } | |
| func doInitConfig() { | |
| flag.Parse() | |
| serverURL = getConfig(*flagServerURL, "SERVER_URL", "http://localhost:8080") | |
| clientID = getConfig(*flagClientID, "CLIENT_ID", "") | |
| clientSecret = getConfig(*flagClientSecret, "CLIENT_SECRET", "") | |
| configWarnings = append(configWarnings, computeConfigWarningsFromFlags(*flagClientSecret)...) |
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using io.ReadAll(io.LimitReader(..., maxResponseSize)) will silently truncate oversized responses; the resulting error will likely be a JSON parse failure rather than a clear "response too large" signal. Consider reading maxResponseSize+1 bytes and returning an explicit error when the limit is exceeded.
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| if int64(len(body)) > maxResponseSize { | |
| return nil, fmt.Errorf("response too large") | |
| } |
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New security behavior (capping response reads) is introduced here, but there are currently no unit tests covering the oversized-response case. Consider adding httptest-based tests that return >maxResponseSize bodies and assert you get a deterministic "response too large"-style error (rather than a JSON parse error).
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| if int64(len(body)) > maxResponseSize { | |
| return nil, fmt.Errorf("failed to read response: response body exceeds maximum allowed size of %d bytes", maxResponseSize) | |
| } |
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using io.ReadAll(io.LimitReader(..., maxResponseSize)) will silently truncate oversized responses; the resulting error will likely be a JSON parse failure rather than a clear "response too large" signal. Consider reading maxResponseSize+1 bytes and returning an explicit error when the limit is exceeded.
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1)) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read response: %w", err) | |
| } | |
| if int64(len(body)) > maxResponseSize { | |
| return nil, fmt.Errorf("response too large: limit %d bytes", maxResponseSize) | |
| } |
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using io.ReadAll(io.LimitReader(..., maxResponseSize)) will silently truncate oversized responses; the resulting error will likely be a JSON parse failure rather than a clear "response too large" signal. Consider reading maxResponseSize+1 bytes and returning an explicit error when the limit is exceeded.
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using io.ReadAll(io.LimitReader(..., maxResponseSize)) will silently truncate oversized responses; the resulting error will likely be a JSON parse failure rather than a clear "response too large" signal. Consider reading maxResponseSize+1 bytes and returning an explicit error when the limit is exceeded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maxResponseSize is defined as 1<<20 (1,048,576 bytes), which is 1 MiB rather than 1 MB. Either adjust the comment to say 1 MiB, or use a decimal value (e.g. 1_000_000) if you intend an SI MB limit.