address review feedback: rename to With* convention, extract env const, redact query params, fix misleading test
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 17s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 38s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m22s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m0s
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 17s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 38s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m22s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m0s
This commit is contained in:
+19
-11
@@ -33,6 +33,9 @@ const (
|
||||
// maxResponseBodyBytes limits how much of a successful response body we read
|
||||
// for defense-in-depth against servers returning excessively large payloads.
|
||||
maxResponseBodyBytes = 10 * 1024 * 1024 // 10 MB
|
||||
|
||||
// envAllowInsecure is the environment variable that gates the WithAllowInsecureHTTP option.
|
||||
envAllowInsecure = "REVIEW_BOT_ALLOW_INSECURE"
|
||||
)
|
||||
|
||||
// APIError represents an HTTP error response from the GitHub API.
|
||||
@@ -95,13 +98,13 @@ type clientConfig struct {
|
||||
// ClientOption configures optional behavior of NewClient.
|
||||
type ClientOption func(*clientConfig)
|
||||
|
||||
// AllowInsecureHTTP permits the client to send credentials over HTTP (non-TLS) URLs.
|
||||
// WithAllowInsecureHTTP permits the client to send credentials over HTTP (non-TLS) URLs.
|
||||
// This is gated by the REVIEW_BOT_ALLOW_INSECURE=1 environment variable as a
|
||||
// defense-in-depth safeguard. If the env var is not set, the option is ignored
|
||||
// and a warning is logged.
|
||||
//
|
||||
// For production use on trusted internal networks only.
|
||||
func AllowInsecureHTTP() ClientOption {
|
||||
func WithAllowInsecureHTTP() ClientOption {
|
||||
return func(c *clientConfig) {
|
||||
c.allowInsecureHTTP = true
|
||||
}
|
||||
@@ -162,7 +165,7 @@ func defaultCheckRedirect(req *http.Request, via []*http.Request) error {
|
||||
// NewClient creates a new GitHub API client.
|
||||
// If baseURL is empty, it defaults to https://api.github.com.
|
||||
// For GitHub Enterprise, pass the API base URL (e.g. https://github.concur.com/api/v3).
|
||||
// The baseURL must use HTTPS unless AllowInsecureHTTP() or AllowInsecureHTTPForTest() (test-only)
|
||||
// The baseURL must use HTTPS unless WithAllowInsecureHTTP() or WithAllowInsecureHTTPForTest() (test-only)
|
||||
// is passed as an option.
|
||||
func NewClient(token, baseURL string, opts ...ClientOption) *Client {
|
||||
if baseURL == "" {
|
||||
@@ -173,18 +176,18 @@ func NewClient(token, baseURL string, opts ...ClientOption) *Client {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
// Environment gate for AllowInsecureHTTP (defense-in-depth).
|
||||
// AllowInsecureHTTPForTest bypasses this gate for test convenience.
|
||||
// Environment gate for WithAllowInsecureHTTP (defense-in-depth).
|
||||
// WithAllowInsecureHTTPForTest bypasses this gate for test convenience.
|
||||
allowInsecure := false
|
||||
if cfg.allowInsecureHTTP {
|
||||
if cfg.testBypass {
|
||||
allowInsecure = true
|
||||
} else if os.Getenv("REVIEW_BOT_ALLOW_INSECURE") == "1" {
|
||||
} else if os.Getenv(envAllowInsecure) == "1" {
|
||||
allowInsecure = true
|
||||
slog.Warn("AllowInsecureHTTP enabled — credentials may be sent over plaintext",
|
||||
"env", "REVIEW_BOT_ALLOW_INSECURE=1")
|
||||
slog.Warn("WithAllowInsecureHTTP enabled — credentials may be sent over plaintext",
|
||||
"env", envAllowInsecure+"=1")
|
||||
} else {
|
||||
slog.Warn("AllowInsecureHTTP option ignored: set REVIEW_BOT_ALLOW_INSECURE=1 to enable")
|
||||
slog.Warn("WithAllowInsecureHTTP option ignored: set "+envAllowInsecure+"=1 to enable")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +269,7 @@ func (c *Client) parseRetryAfter(value string) (time.Duration, bool) {
|
||||
// It avoids the allocation of url.Parse for a simple scheme check.
|
||||
func hasHTTPSScheme(rawURL string) bool {
|
||||
const prefix = "https://"
|
||||
return len(rawURL) >= len(prefix) && strings.EqualFold(rawURL[:len(prefix)], prefix)
|
||||
return len(rawURL) >= len(prefix) && strings.HasPrefix(strings.ToLower(rawURL[:len(prefix)]), prefix)
|
||||
}
|
||||
|
||||
// doRequest performs an HTTP request with retry on 429 rate limit responses.
|
||||
@@ -277,7 +280,12 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
|
||||
// Uses a string prefix check instead of url.Parse to avoid per-request allocations.
|
||||
if c.token != "" && !c.allowInsecureHTTP {
|
||||
if !hasHTTPSScheme(reqURL) {
|
||||
return nil, fmt.Errorf("refusing to send credentials over non-HTTPS URL %q (use AllowInsecureHTTP option for trusted networks)", reqURL)
|
||||
// Redact query parameters to avoid leaking sensitive data in error messages.
|
||||
sanitized := reqURL
|
||||
if i := strings.Index(reqURL, "?"); i >= 0 {
|
||||
sanitized = reqURL[:i] + "?[REDACTED]"
|
||||
}
|
||||
return nil, fmt.Errorf("refusing to send credentials over non-HTTPS URL %q (use WithAllowInsecureHTTP option for trusted networks)", sanitized)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user