fix: address review findings from rounds 2843-2846
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 41s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m13s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m23s
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 41s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m13s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m23s
- Remove redundant timer.Stop() after timer fires (Sonnet #1, GPT #2) - Remove unused TotalCount field from checkRunsResponse (Sonnet #2) - Improve escapePath doc comment to explain deliberate silent stripping (Sonnet #3) - Fix ListContents to handle both array (directory) and object (single file) responses from GitHub Contents API (GPT #3) - Add HTTPS enforcement: refuse to send credentials over non-HTTPS URLs unless AllowInsecureHTTP() option is passed (Security #1) - Replace constant-value test with actual behavior test for response body limiting (Sonnet #6) - Run gofmt for consistent formatting (Sonnet #4) - Add tests for HTTPS enforcement and ListContents single-file handling
This commit is contained in:
+35
-7
@@ -65,13 +65,30 @@ func asAPIError(err error) (*APIError, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// clientConfig holds optional configuration for NewClient.
|
||||
type clientConfig struct {
|
||||
allowInsecureHTTP bool
|
||||
}
|
||||
|
||||
// ClientOption configures optional behavior of NewClient.
|
||||
type ClientOption func(*clientConfig)
|
||||
|
||||
// AllowInsecureHTTP permits the client to use HTTP (non-TLS) base URLs.
|
||||
// This should only be used for trusted internal deployments or testing.
|
||||
func AllowInsecureHTTP() ClientOption {
|
||||
return func(c *clientConfig) {
|
||||
c.allowInsecureHTTP = true
|
||||
}
|
||||
}
|
||||
|
||||
// Client interacts with the GitHub API.
|
||||
// A Client is safe for concurrent use by multiple goroutines;
|
||||
// however, SetHTTPClient and SetRetryBackoff must not be called concurrently with requests.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
httpClient *http.Client
|
||||
baseURL string
|
||||
token string
|
||||
allowInsecureHTTP bool
|
||||
httpClient *http.Client
|
||||
|
||||
// retryBackoff defines the delays between retry attempts for 429 responses.
|
||||
// retryBackoff[i] is the delay before attempt i+1 (after attempt i fails).
|
||||
@@ -82,13 +99,20 @@ type Client struct {
|
||||
// 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).
|
||||
func NewClient(token, baseURL string) *Client {
|
||||
// The baseURL must use HTTPS; pass AllowInsecureHTTP() as an option to permit HTTP
|
||||
// for trusted internal deployments (e.g. local testing).
|
||||
func NewClient(token, baseURL string, opts ...ClientOption) *Client {
|
||||
if baseURL == "" {
|
||||
baseURL = defaultBaseURL
|
||||
}
|
||||
cfg := clientConfig{}
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
return &Client{
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
token: token,
|
||||
baseURL: strings.TrimRight(baseURL, "/"),
|
||||
allowInsecureHTTP: cfg.allowInsecureHTTP,
|
||||
token: token,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
@@ -146,7 +170,7 @@ func (c *Client) doRequest(ctx context.Context, method, url string, accept strin
|
||||
timer := time.NewTimer(delay)
|
||||
select {
|
||||
case <-timer.C:
|
||||
timer.Stop()
|
||||
// Timer already fired; Stop() is a no-op here.
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return nil, ctx.Err()
|
||||
@@ -159,6 +183,10 @@ func (c *Client) doRequest(ctx context.Context, method, url string, accept strin
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
if c.token != "" {
|
||||
// Refuse to send credentials over plaintext unless explicitly allowed.
|
||||
if !c.allowInsecureHTTP && req.URL.Scheme != "https" {
|
||||
return nil, fmt.Errorf("refusing to send credentials over non-HTTPS URL %q (use AllowInsecureHTTP option for trusted networks)", req.URL.Host)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
Reference in New Issue
Block a user