feat(httpclient): add proxy and TLS support (OD-30)#205
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runs the actual cli-v2 binary through a real mitmproxy against app.codacy.com. Baseline confirms SSL_CERT_FILE is ignored on macOS and CODACY_CLI_INSECURE is unimplemented; both cases flip green once OD-30 lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-30) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ction (OD-30) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| UnusedCode | 2 medium (2 false positives) |
| BestPractice | 1 medium |
| Documentation | 4 minor |
| Security | 1 critical (1 false positive) 1 medium |
🟢 Metrics 26 complexity · 4 duplication
Metric Results Complexity 26 Duplication 4
🟢 Coverage 62.14% diff coverage · +0.59% coverage variation
Metric Results Coverage variation ✅ +0.59% coverage variation (-0.50%) Diff coverage ✅ 62.14% diff coverage (50.00%) Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (ebec24b) Report Missing Report Missing Report Missing Head commit (1c30605) 6301 (+90) 1561 (+59) 24.77% (+0.59%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#205) 103 64 62.14% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%1 Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Code Review
This pull request introduces a centralized HTTP client factory in utils/httpclient that honors standard proxy environment variables, supports custom CA bundles via SSL_CERT_FILE, and allows disabling TLS verification via CODACY_CLI_INSECURE. Multiple callsites across the codebase have been migrated to use this new factory. Feedback on the changes suggests optimizing the TLS configuration by only loading the system cert pool when a custom CA bundle is specified to avoid performance overhead and potential handshake failures. Additionally, it is recommended to clone http.DefaultTransport instead of creating a new transport from scratch to preserve optimized default settings such as connection pooling and HTTP/2 support.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pool, err := x509.SystemCertPool() | ||
| if err != nil || pool == nil { | ||
| pool = x509.NewCertPool() | ||
| } | ||
|
|
||
| if path := caBundlePath(); path != "" { | ||
| pemBytes, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to read CA bundle from %s (%s): %w", EnvCABundle, path, err) | ||
| } | ||
| if !pool.AppendCertsFromPEM(pemBytes) { | ||
| return nil, fmt.Errorf("no valid certificates found in CA bundle %s (%s)", EnvCABundle, path) | ||
| } | ||
| } | ||
|
|
||
| cfg.RootCAs = pool | ||
| return cfg, nil |
There was a problem hiding this comment.
Loading the system cert pool on every client creation even when no custom CA bundle (SSL_CERT_FILE) is specified is inefficient and can be slow on some operating systems. Furthermore, if x509.SystemCertPool() fails and no custom CA is provided, cfg.RootCAs is set to an empty cert pool, which will cause all subsequent TLS handshakes to fail instead of falling back to Go's default system verification behavior. We should only load the system cert pool and set cfg.RootCAs if a custom CA bundle path is actually configured.
| pool, err := x509.SystemCertPool() | |
| if err != nil || pool == nil { | |
| pool = x509.NewCertPool() | |
| } | |
| if path := caBundlePath(); path != "" { | |
| pemBytes, err := os.ReadFile(path) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read CA bundle from %s (%s): %w", EnvCABundle, path, err) | |
| } | |
| if !pool.AppendCertsFromPEM(pemBytes) { | |
| return nil, fmt.Errorf("no valid certificates found in CA bundle %s (%s)", EnvCABundle, path) | |
| } | |
| } | |
| cfg.RootCAs = pool | |
| return cfg, nil | |
| if path := caBundlePath(); path != "" { | |
| pool, err := x509.SystemCertPool() | |
| if err != nil || pool == nil { | |
| pool = x509.NewCertPool() | |
| } | |
| pemBytes, err := os.ReadFile(path) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read CA bundle from %s (%s): %w", EnvCABundle, path, err) | |
| } | |
| if !pool.AppendCertsFromPEM(pemBytes) { | |
| return nil, fmt.Errorf("no valid certificates found in CA bundle %s (%s)", EnvCABundle, path) | |
| } | |
| cfg.RootCAs = pool | |
| } | |
| return cfg, nil |
There was a problem hiding this comment.
Applied — only build the cert pool and set RootCAs when SSL_CERT_FILE is configured; otherwise leave RootCAs nil so Go uses default system verification. Avoids the empty-pool-rejects-everything failure mode.
| transport := &http.Transport{ | ||
| Proxy: http.ProxyFromEnvironment, | ||
| TLSClientConfig: tlsCfg, | ||
| } |
There was a problem hiding this comment.
Creating a new http.Transport from scratch without cloning http.DefaultTransport or copying its configuration means you lose important default settings, such as connection pooling limits (MaxIdleConns), idle timeouts (IdleConnTimeout), TLS handshake timeouts (TLSHandshakeTimeout), and HTTP/2 support (ForceAttemptHTTP2). To preserve these optimized defaults, clone http.DefaultTransport and then apply your custom TLSClientConfig.
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = tlsCfgThere was a problem hiding this comment.
Applied — now clone http.DefaultTransport and override only Proxy + TLSClientConfig, preserving connection pooling, idle/handshake timeouts, and HTTP/2.
…m CA (OD-30) Address PR #205 review: - Clone http.DefaultTransport to preserve connection pooling, idle/handshake timeouts, and HTTP/2 instead of building a bare Transport. - Only set tls.Config.RootCAs when SSL_CERT_FILE is configured; leave nil otherwise so Go uses default system verification. Prevents an empty pool (when SystemCertPool fails) from rejecting all TLS handshakes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing to file (OD-30) Proxy/TLS warnings (and all log.Printf/log.Fatalf output) reached only the terminal, never codacy-cli.log. Initialize now redirects Go's standard logger to io.MultiWriter(stderr, logfile), and the CODACY_CLI_INSECURE warning is emitted via log.Println so it lands in the file too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
utils/httpclientpackage:New(opts...)factory that setshttp.ProxyFromEnvironment, loads system cert pool + optionalSSL_CERT_FILEcustom CA bundle, and honoursCODACY_CLI_INSECURE=1(with stderr warning)codacy-client,tools/patterns,utils/download,cmd/upload×4,cmd/upload_sbom) to the new factoryutils/httpclient/httpclient_test.go, 9 tests, no network) and a real-life integration harness (integration-tests/proxy-tls/run.sh) using mitmproxy againstapp.codacy.comOD-30 Proxy/TLS — Manual Test Results
Real cli-v2 init → mitmproxy (:8899, MITM) → real app.codacy.com.
A — proxy + custom CA (SSL_CERT_FILE=$CA)
ℹ️ No project token was specified, fetching codacy default configurations
PMD7 configuration created based on Codacy settings
...
✅ Successfully initialized Codacy configuration!
PASS — traffic through proxy, custom CA trusted, success.
B — proxy, no CA
Warning: Failed to get default patterns for : ... tls: failed to verify
certificate: x509: "app.codacy.com" certificate is not trusted
(repeated per tool)
failed to build languages config from API: ... certificate is not trusted
PASS — TLS correctly rejected MITM cert (fails loud, no insecure default).
C — proxy + CODACY_CLI_INSECURE=1
WARNING: TLS certificate verification is DISABLED (CODACY_CLI_INSECURE set)...
(repeated per HTTP call)
✅ Successfully initialized Codacy configuration!
PASS — insecure toggle works, stderr warning shown, success.
D — NO_PROXY=app.codacy.com,api.codacy.com
ℹ️ No project token was specified, fetching codacy default configurations
✅ Successfully initialized Codacy configuration!
PASS — bypassed proxy (no MITM in path), success.
Test plan
go test ./utils/httpclient/ -count=5— all 9 unit tests pass (stable)go build ./...+go vet ./...— cleanintegration-tests/proxy-tls/run.sh— all 4 cases (custom CA, no-CA-fails, insecure, no-proxy bypass) PASS with real mitmproxy → real app.codacy.comx509: certificate is not trusted; after: both PASSCloses OD-30
🤖 Generated with Claude Code