feat(github): add safeguards against accidental AllowInsecureHTTP use (#96) #113

Merged
aweiker merged 6 commits from review-bot-issue-96 into main 2026-05-13 20:21:42 +00:00
3 changed files with 246 additions and 15 deletions
+79 -6
View File
@@ -8,7 +8,10 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -93,10 +96,14 @@ type Client struct {
// Higher-level exported methods (GetPullRequest, etc.) will use it to
// construct request URLs; remove this field if those methods end up
// accepting full URLs instead.
baseURL string
token string
baseURL string
token string
httpClient *http.Client
// allowInsecureHTTP permits requests to HTTP (non-TLS) endpoints.
// When false, doRequest rejects URLs with an http:// scheme.
allowInsecureHTTP bool
// retryBackoff defines the delays between retry attempts for 429 responses.
// retryBackoff[i] is the delay before attempt i+1 (after attempt i fails).
// If nil, defaults to {1s, 2s}.
1
@@ -135,16 +142,53 @@ func defaultCheckRedirect(req *http.Request, via []*http.Request) error {
return nil
}
// ClientOption configures optional behavior of a Client.
type ClientOption func(*clientConfig)
type clientConfig struct {
allowInsecureHTTP bool
insecureIsTestBypass bool
}
// AllowInsecureHTTP permits sending credentials over plaintext HTTP connections.
// In production, this option is gated by the REVIEW_BOT_ALLOW_INSECURE=1
// environment variable. Without the env var set, the option is ignored
// and a warning is logged.
//
Review

[MINOR] AllowInsecureHTTPForTest is in the production file (client.go). Per the convention, test-only helpers should ideally live in an export_test.go file or be clearly gated. Since this function is exported and intended exclusively for test code, it bleeds test surface into the production API. Consider moving it to a file compiled only during tests (e.g., export_test.go), or renaming to make its test-only nature even more prominent in godoc.

**[MINOR]** AllowInsecureHTTPForTest is in the production file (client.go). Per the convention, test-only helpers should ideally live in an export_test.go file or be clearly gated. Since this function is exported and intended exclusively for test code, it bleeds test surface into the production API. Consider moving it to a file compiled only during tests (e.g., export_test.go), or renaming to make its test-only nature even more prominent in godoc.
// For tests, use AllowInsecureHTTPForTest (defined in a _test.go file in the same package) which bypasses the env gate.
func AllowInsecureHTTP() ClientOption {
return func(cfg *clientConfig) {
Review

[MINOR] AllowInsecureHTTPForTest is exported but intended only for tests. Consider making it unexported (allowInsecureHTTPForTest) and using it from package-internal tests, or clearly document and enforce via build tags/export_test.go if cross-package tests require it, to reduce risk of accidental production use.

**[MINOR]** AllowInsecureHTTPForTest is exported but intended only for tests. Consider making it unexported (allowInsecureHTTPForTest) and using it from package-internal tests, or clearly document and enforce via build tags/export_test.go if cross-package tests require it, to reduce risk of accidental production use.
Review

[NIT] For functional options, consider a With* naming convention (e.g., WithInsecureHTTP) to align with common Go patterns and improve discoverability (see configuration.md, Functional Options).

**[NIT]** For functional options, consider a With* naming convention (e.g., WithInsecureHTTP) to align with common Go patterns and improve discoverability (see configuration.md, Functional Options).
cfg.allowInsecureHTTP = true
}
Review

[NIT] The doc comment for AllowInsecureHTTP() has an overly long line: 'For tests, use AllowInsecureHTTPForTest (defined in a _test.go file in the same package) which bypasses the env gate.' This wraps beyond 80 chars and doesn't follow the conventional line-length style seen elsewhere in the file, though this is cosmetic only.

**[NIT]** The doc comment for AllowInsecureHTTP() has an overly long line: 'For tests, use AllowInsecureHTTPForTest (defined in a _test.go file in the same package) which bypasses the env gate.' This wraps beyond 80 chars and doesn't follow the conventional line-length style seen elsewhere in the file, though this is cosmetic only.
}
Review

[MINOR] The doc comment for AllowInsecureHTTP() says the option is 'silently ignored' but the very next sentence says 'a warning is logged'. These are contradictory — being warned in logs is not silent. The comment should say something like 'the option is ignored and a warning is logged via slog.Warn'.

**[MINOR]** The doc comment for `AllowInsecureHTTP()` says the option is 'silently ignored' but the very next sentence says 'a warning is logged'. These are contradictory — being warned in logs is not silent. The comment should say something like 'the option is ignored and a warning is logged via slog.Warn'.
// 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 {
func NewClient(token, baseURL string, opts ...ClientOption) *Client {
Review

[MINOR] AllowInsecureHTTPForTest bypasses the env gate and does not emit a warning; as an exported option it could be mistakenly used in production, silently allowing plaintext HTTP. Consider restricting its availability to tests (e.g., define in a _test.go file) or emitting a warning when enabled to preserve operator visibility.

**[MINOR]** AllowInsecureHTTPForTest bypasses the env gate and does not emit a warning; as an exported option it could be mistakenly used in production, silently allowing plaintext HTTP. Consider restricting its availability to tests (e.g., define in a _test.go file) or emitting a warning when enabled to preserve operator visibility.
if baseURL == "" {
baseURL = defaultBaseURL
}
Review

[NIT] The doc comment for AllowInsecureHTTP() says 'silently ignored' but the code actually logs a slog.Warn — it's not silent. The comment should say 'ignored with a warning log' to match actual behavior.

**[NIT]** The doc comment for `AllowInsecureHTTP()` says 'silently ignored' but the code actually logs a `slog.Warn` — it's not silent. The comment should say 'ignored with a warning log' to match actual behavior.
var cfg clientConfig
for _, opt := range opts {
opt(&cfg)
Review

[MINOR] The doc comment for AllowInsecureHTTP() says 'For tests, use AllowInsecureHTTPForTest (defined in export_test.go)' but export_test.go is actually in the package itself (package github), not a separate file the doc comment navigation implies. This is fine technically but the phrasing slightly misrepresents the location — it's defined in a _test.go file in the same package. Minor wording issue only.

**[MINOR]** The doc comment for AllowInsecureHTTP() says 'For tests, use AllowInsecureHTTPForTest (defined in export_test.go)' but export_test.go is actually in the package itself (package github), not a separate file the doc comment navigation implies. This is fine technically but the phrasing slightly misrepresents the location — it's defined in a _test.go file in the same package. Minor wording issue only.
}
if cfg.allowInsecureHTTP && !cfg.insecureIsTestBypass {
if os.Getenv("REVIEW_BOT_ALLOW_INSECURE") != "1" {
slog.Warn("AllowInsecureHTTP ignored: set REVIEW_BOT_ALLOW_INSECURE=1 to enable")
cfg.allowInsecureHTTP = false
} else {
slog.Warn("AllowInsecureHTTP enabled — credentials may be sent over plaintext",
"env", "REVIEW_BOT_ALLOW_INSECURE=1")
}
}
return &Client{
baseURL: strings.TrimRight(baseURL, "/"),
token: token,
baseURL: strings.TrimRight(baseURL, "/"),
token: token,
allowInsecureHTTP: cfg.allowInsecureHTTP,
httpClient: &http.Client{
Timeout: 30 * time.Second,
Review

[NIT] The warn log 'AllowInsecureHTTP ignored: set REVIEW_BOT_ALLOW_INSECURE=1 to enable' fires silently in tests that run in environments where the env var isn't set. Since NewClient is called in many tests (e.g. TestNewClient_DefaultBaseURL, TestParseRetryAfter_* etc.) without AllowInsecureHTTP(), this won't fire there. But any caller using AllowInsecureHTTP() in a test without the env var will produce a slog.Warn to whatever slog default is configured. The AllowInsecureHTTPForTest bypass prevents this for test servers, so this is acceptable — just worth noting that the warn will fire in non-test environments where someone misconfigures the option.

**[NIT]** The warn log 'AllowInsecureHTTP ignored: set REVIEW_BOT_ALLOW_INSECURE=1 to enable' fires silently in tests that run in environments where the env var isn't set. Since NewClient is called in many tests (e.g. TestNewClient_DefaultBaseURL, TestParseRetryAfter_* etc.) without AllowInsecureHTTP(), this won't fire there. But any caller using AllowInsecureHTTP() in a test without the env var will produce a slog.Warn to whatever slog default is configured. The AllowInsecureHTTPForTest bypass prevents this for test servers, so this is acceptable — just worth noting that the warn will fire in non-test environments where someone misconfigures the option.
CheckRedirect: defaultCheckRedirect,
@@ -215,10 +259,39 @@ func (c *Client) parseRetryAfter(value string) (time.Duration, bool) {
return 0, false
}
Review

[MINOR] redactURL sets u.Fragment = "" unconditionally. Fragments are not sent over the wire in HTTP requests (RFC 7230), so stripping them is harmless but adds noise. Not a bug, but the fragment stripping is unnecessary for this use case (URL in error messages for HTTP requests).

**[MINOR]** redactURL sets u.Fragment = "" unconditionally. Fragments are not sent over the wire in HTTP requests (RFC 7230), so stripping them is harmless but adds noise. Not a bug, but the fragment stripping is unnecessary for this use case (URL in error messages for HTTP requests).
// redactURL redacts sensitive components from a URL for safe inclusion in error
// messages and log output. It removes userinfo (e.g., user:pass@) and replaces
// query parameters with a placeholder.
func redactURL(rawURL string) string {
u, err := url.Parse(rawURL)
if err != nil {
return "<unparseable URL>"
}
u.User = nil
Review

[NIT] redactURL redacts userinfo and query parameters; consider also clearing URL fragments (u.Fragment) to avoid leaking anchor components in logs, even though they’re generally not sent to servers.

**[NIT]** redactURL redacts userinfo and query parameters; consider also clearing URL fragments (u.Fragment) to avoid leaking anchor components in logs, even though they’re generally not sent to servers.
if u.RawQuery != "" {
u.RawQuery = "<redacted>"
}
return u.String()
}
// doRequest performs an HTTP request with retry on 429 rate limit responses.
// It respects the Retry-After header when present, supporting both integer
Review

[NIT] The HTTP rejection error message says 'use HTTPS or set AllowInsecureHTTP option'. For users who call AllowInsecureHTTP() and see it silently fall back (env var not set), this message may be confusing — they did set the option but it was ignored. A slightly more helpful message might mention the REVIEW_BOT_ALLOW_INSECURE env var. Minor UX nit.

**[NIT]** The HTTP rejection error message says 'use HTTPS or set AllowInsecureHTTP option'. For users who call AllowInsecureHTTP() and see it silently fall back (env var not set), this message may be confusing — they did set the option but it was ignored. A slightly more helpful message might mention the REVIEW_BOT_ALLOW_INSECURE env var. Minor UX nit.
// seconds and HTTP-date formats (capped at maxRetryAfter).
func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept string) ([]byte, error) {
// NOTE: This parses reqURL a second time (http.NewRequestWithContext parses it
// again internally). Acceptable cost: URL parsing is cheap and threading the
// parsed *url.URL through would complicate the interface for negligible gain.
if !c.allowInsecureHTTP {
parsed, err := url.Parse(reqURL)
if err != nil {
return nil, fmt.Errorf("parse request URL: %w", err)
Review

[MINOR] The error message includes user-influenced URL data via redactURL(reqURL) without explicit newline/carriage-return sanitization. If upstream code logs this error directly, it could allow limited log injection if a crafted URL with control characters is accepted by url.Parse. Consider sanitizing \n/\r in the formatted error or ensuring redactURL strips such characters.

**[MINOR]** The error message includes user-influenced URL data via redactURL(reqURL) without explicit newline/carriage-return sanitization. If upstream code logs this error directly, it could allow limited log injection if a crafted URL with control characters is accepted by url.Parse. Consider sanitizing \n/\r in the formatted error or ensuring redactURL strips such characters.
}
if strings.EqualFold(parsed.Scheme, "http") {
Review

[NIT] The HTTP scheme check in doRequest parses the URL twice when the URL is also valid for the request — once here in the guard and implicitly again by http.NewRequestWithContext. This is a minor inefficiency in the hot path but not a correctness issue.

**[NIT]** The HTTP scheme check in `doRequest` parses the URL twice when the URL is also valid for the request — once here in the guard and implicitly again by `http.NewRequestWithContext`. This is a minor inefficiency in the hot path but not a correctness issue.
return nil, fmt.Errorf("refusing HTTP request to %s: use HTTPS or set AllowInsecureHTTP option", redactURL(reqURL))
}
}
var backoff []time.Duration
if c.retryBackoff != nil {
backoff = append([]time.Duration(nil), c.retryBackoff...)
@@ -237,7 +310,7 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
timer := time.NewTimer(delay)
Review

[NIT] Calling timer.Stop() after the timer has already fired is a no-op (as noted in the comment). It’s harmless but could be removed for clarity; alternatively, keep as-is for symmetry.

**[NIT]** Calling timer.Stop() after the timer has already fired is a no-op (as noted in the comment). It’s harmless but could be removed for clarity; alternatively, keep as-is for symmetry.
select {
case <-timer.C:
timer.Stop()
timer.Stop() // no-op after fire; kept for symmetry with the ctx.Done case
case <-ctx.Done():
timer.Stop()
return nil, ctx.Err()
1
+154 -9
View File
@@ -35,7 +35,7 @@ func TestDoRequest_Success(t *testing.T) {
}))
defer srv.Close()
c := NewClient("test-token", srv.URL)
c := NewClient("test-token", srv.URL, AllowInsecureHTTPForTest())
body, err := c.doGet(context.Background(), srv.URL+"/test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -60,7 +60,7 @@ func TestDoRequest_429_RetryAfter_IntegerSeconds(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.SetRetryBackoff([]time.Duration{0, 0})
body, err := c.doGet(context.Background(), srv.URL+"/test")
@@ -94,7 +94,7 @@ func TestDoRequest_429_RetryAfter_HTTPDate(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.now = func() time.Time { return fixedNow }
// Initial backoff is 0; the HTTP-date parser will compute 1s and override.
c.SetRetryBackoff([]time.Duration{0, 0})
@@ -130,7 +130,7 @@ func TestDoRequest_429_RetryAfter_HTTPDate_InPast(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.now = func() time.Time { return fixedNow }
c.SetRetryBackoff([]time.Duration{0, 0})
@@ -157,7 +157,7 @@ func TestDoRequest_429_NoRetryAfter_UsesDefaultBackoff(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.SetRetryBackoff([]time.Duration{0, 0})
body, err := c.doGet(context.Background(), srv.URL+"/test")
@@ -187,7 +187,7 @@ func TestDoRequest_429_InvalidRetryAfter_UsesDefaultBackoff(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.SetRetryBackoff([]time.Duration{0, 0})
body, err := c.doGet(context.Background(), srv.URL+"/test")
@@ -208,7 +208,7 @@ func TestDoRequest_404_NoRetry(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
_, err := c.doGet(context.Background(), srv.URL+"/test")
if err == nil {
t.Fatal("expected error, got nil")
@@ -230,7 +230,7 @@ func TestDoRequest_401_NoRetry(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
_, err := c.doGet(context.Background(), srv.URL+"/test")
if err == nil {
t.Fatal("expected error, got nil")
@@ -260,7 +260,7 @@ func TestDoRequest_ContextCanceled(t *testing.T) {
}))
defer srv.Close()
c := NewClient("tok", srv.URL)
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
c.SetRetryBackoff([]time.Duration{10 * time.Second, 10 * time.Second})
ctx, cancel := context.WithCancel(context.Background())
@@ -511,3 +511,148 @@ func TestSetHTTPClient_NilRestoresDefault(t *testing.T) {
t.Fatal("expected CheckRedirect policy after SetHTTPClient(nil)")
}
}
Review

[NIT] The three new TestDoRequest_RejectsHTTPWithToken* / TestDoRequest_AllowsHTTP* tests and the TestWithAllowInsecureHTTP_* tests each spin up a dedicated httptest.Server even when the server is never contacted (e.g., the 'rejects' tests). This is harmless but slightly wasteful. An alternative would be to test doGet directly against a static HTTP URL like "http://example.com/test" without a real server for rejection tests. Not a bug.

**[NIT]** The three new `TestDoRequest_RejectsHTTPWithToken*` / `TestDoRequest_AllowsHTTP*` tests and the `TestWithAllowInsecureHTTP_*` tests each spin up a dedicated `httptest.Server` even when the server is never contacted (e.g., the 'rejects' tests). This is harmless but slightly wasteful. An alternative would be to test `doGet` directly against a static HTTP URL like `"http://example.com/test"` without a real server for rejection tests. Not a bug.
Review

[NIT] TestAllowInsecureHTTPForTest_PermitsHTTP is functionally identical to TestDoRequest_Success (both create an httptest.NewServer, use AllowInsecureHTTPForTest, and check the body). While the duplication is harmless, consolidating or removing the redundant test would reduce noise.

**[NIT]** `TestAllowInsecureHTTPForTest_PermitsHTTP` is functionally identical to `TestDoRequest_Success` (both create an httptest.NewServer, use AllowInsecureHTTPForTest, and check the body). While the duplication is harmless, consolidating or removing the redundant test would reduce noise.
func TestAllowInsecureHTTPForTest_PermitsHTTP(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}))
defer srv.Close()
c := NewClient("tok", srv.URL, AllowInsecureHTTPForTest())
body, err := c.doGet(context.Background(), srv.URL+"/test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != "ok" {
t.Errorf("body = %q, want %q", body, "ok")
}
}
func TestNoInsecureOption_RejectsHTTP(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("request should not have been sent")
}))
defer srv.Close()
Review

[NIT] TestNoInsecureOption_RejectsUppercaseHTTP and TestNoInsecureOption_RejectsMixedCaseHTTP don't start an httptest.Server — they just pass 'HTTP://example.com/test' directly. These tests don't need a real server (good), but using an unreachable external host in test code is a minor smell. Consider using 'HTTP://127.0.0.1:1/test' or similar to make it obvious no real connection is attempted. Not a bug since the scheme check fires before any TCP connection.

**[NIT]** TestNoInsecureOption_RejectsUppercaseHTTP and TestNoInsecureOption_RejectsMixedCaseHTTP don't start an httptest.Server — they just pass 'HTTP://example.com/test' directly. These tests don't need a real server (good), but using an unreachable external host in test code is a minor smell. Consider using 'HTTP://127.0.0.1:1/test' or similar to make it obvious no real connection is attempted. Not a bug since the scheme check fires before any TCP connection.
c := NewClient("tok", srv.URL)
_, err := c.doGet(context.Background(), srv.URL+"/test")
if err == nil {
t.Fatal("expected error for HTTP request without AllowInsecureHTTP")
}
if !strings.Contains(err.Error(), "refusing HTTP request") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestNoInsecureOption_RejectsUppercaseHTTP(t *testing.T) {
// Verify case-insensitive scheme check (RFC 3986).
c := NewClient("tok", "HTTP://127.0.0.1:1")
_, err := c.doGet(context.Background(), "HTTP://127.0.0.1:1/test")
if err == nil {
t.Fatal("expected error for uppercase HTTP scheme")
}
Review

[NIT] Missing blank line between TestNoInsecureOption_RejectsHTTP and TestNoInsecureOption_RejectsUppercaseHTTP — minor formatting inconsistency compared to the rest of the file, but gofmt doesn't enforce blank lines between top-level declarations in the same way.

**[NIT]** Missing blank line between `TestNoInsecureOption_RejectsHTTP` and `TestNoInsecureOption_RejectsUppercaseHTTP` — minor formatting inconsistency compared to the rest of the file, but `gofmt` doesn't enforce blank lines between top-level declarations in the same way.
if !strings.Contains(err.Error(), "refusing HTTP request") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestNoInsecureOption_RejectsMixedCaseHTTP(t *testing.T) {
// Verify mixed case like "Http://" is also rejected.
c := NewClient("tok", "Http://127.0.0.1:1")
_, err := c.doGet(context.Background(), "Http://127.0.0.1:1/test")
if err == nil {
t.Fatal("expected error for mixed-case HTTP scheme")
}
if !strings.Contains(err.Error(), "refusing HTTP request") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestAllowInsecureHTTP_WithoutEnvVar_Rejected(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("request should not have been sent")
}))
defer srv.Close()
t.Setenv("REVIEW_BOT_ALLOW_INSECURE", "")
c := NewClient("tok", srv.URL, AllowInsecureHTTP())
_, err := c.doGet(context.Background(), srv.URL+"/test")
if err == nil {
t.Fatal("expected error: AllowInsecureHTTP without env var should be rejected")
}
if !strings.Contains(err.Error(), "refusing HTTP request") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestAllowInsecureHTTP_WithEnvVar_Permitted(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("insecure-ok"))
}))
defer srv.Close()
t.Setenv("REVIEW_BOT_ALLOW_INSECURE", "1")
c := NewClient("tok", srv.URL, AllowInsecureHTTP())
body, err := c.doGet(context.Background(), srv.URL+"/test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(body) != "insecure-ok" {
t.Errorf("body = %q, want %q", body, "insecure-ok")
}
}
Review

[NIT] Double blank line before TestAllowInsecureHTTP_WithoutEnvVar_Rejected — minor inconsistency with the rest of the file's style.

**[NIT]** Double blank line before `TestAllowInsecureHTTP_WithoutEnvVar_Rejected` — minor inconsistency with the rest of the file's style.
func TestAllowInsecureHTTP_EnvVarNotOne_Rejected(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("request should not have been sent")
}))
defer srv.Close()
// "true" is not "1" — strict check
t.Setenv("REVIEW_BOT_ALLOW_INSECURE", "true")
c := NewClient("tok", srv.URL, AllowInsecureHTTP())
_, err := c.doGet(context.Background(), srv.URL+"/test")
if err == nil {
t.Fatal("expected error: env var 'true' is not '1'")
}
if !strings.Contains(err.Error(), "refusing HTTP request") {
t.Errorf("unexpected error message: %v", err)
}
}
func TestRedactURL_WithQuery(t *testing.T) {
got := redactURL("http://localhost:1234/path?secret=token&foo=bar")
want := "http://localhost:1234/path?<redacted>"
if got != want {
t.Errorf("redactURL = %q, want %q", got, want)
}
}
func TestRedactURL_NoQuery(t *testing.T) {
got := redactURL("http://localhost:1234/path")
want := "http://localhost:1234/path"
if got != want {
t.Errorf("redactURL = %q, want %q", got, want)
}
}
func TestRedactURL_Userinfo(t *testing.T) {
got := redactURL("http://user:pass@localhost:1234/path")
want := "http://localhost:1234/path"
if got != want {
t.Errorf("redactURL = %q, want %q", got, want)
}
}
func TestRedactURL_UserinfoWithQuery(t *testing.T) {
got := redactURL("http://user:pass@localhost:1234/path?secret=token")
want := "http://localhost:1234/path?<redacted>"
if got != want {
t.Errorf("redactURL = %q, want %q", got, want)
}
}
+13
View File
@@ -0,0 +1,13 @@
package github
// AllowInsecureHTTPForTest permits sending credentials over plaintext HTTP
// without requiring the REVIEW_BOT_ALLOW_INSECURE environment variable.
// This is intended exclusively for test code using httptest.Server.
//
// Defined in a _test.go file so it is only available to test binaries.
func AllowInsecureHTTPForTest() ClientOption {
return func(cfg *clientConfig) {
cfg.allowInsecureHTTP = true
cfg.insecureIsTestBypass = true
}
}