refactor: centralize HTTP transport policies#2021
Conversation
📝 WalkthroughWalkthroughThe PR introduces platform/external HTTP request classification, policy-routed clients, scoped extension middleware, SDK bootstrap bridging, stricter redirect handling, and fail-closed transport cloning for downloads. ChangesProvider-aware transport routing
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Command
participant Factory
participant HTTPPolicyRouter
participant ExtensionMiddleware
participant ExternalServer
Command->>Factory: Request ExternalHTTPClient
Factory->>HTTPPolicyRouter: Force external RequestClass
HTTPPolicyRouter->>ExtensionMiddleware: Apply eligible provider middleware
ExtensionMiddleware->>ExternalServer: Send external request
ExternalServer-->>Command: Return response
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@e0525492bb7b86424cc0ffe945cfaf3fba9cfb66🧩 Skill updatenpx skills add larksuite/cli#refactor/http-transport-policy -y -g |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2021 +/- ##
==========================================
- Coverage 75.06% 75.06% -0.01%
==========================================
Files 902 905 +3
Lines 95944 96292 +348
==========================================
+ Hits 72025 72279 +254
- Misses 18380 18434 +54
- Partials 5539 5579 +40 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
internal/validate/url.go (2)
1-1: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftExtract a shared "hardened transport leaf" helper. Both sites independently implement the same clone-and-guard pattern (wrap
DialContext/DialTLSContext/DialTLSwith remote-IP validation, fail closed when safe cloning isn't possible), which has already caused the two implementations to diverge on error typing.
internal/validate/url.go#L195-271: extractnewDownloadTransportLeaf/configureDirectDownloadTransport's dial-wrapping/guard logic into a shared helper (e.g., ininternal/transportorinternal/validate) parameterized by the remote-IP predicate and proxy-handling policy (freeze-selected-proxy vs. disable-proxy).shortcuts/doc/doc_resource_cover.go#L727-801: adopt the same shared helper forcloneDocCoverTransport/newDocCoverTransportLeafinstead of re-implementing the dial/guard wiring, keeping its existing "always disable proxy" policy.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/validate/url.go` at line 1, Extract the duplicated transport clone-and-guard wiring from newDownloadTransportLeaf/configureDirectDownloadTransport and cloneDocCoverTransport/newDocCoverTransportLeaf into one shared helper. Parameterize it with the remote-IP validation predicate and proxy policy, preserving frozen selected-proxy behavior for downloads and always-disabled proxy behavior for document covers; ensure cloning failures fail closed and all DialContext/DialTLSContext/DialTLS wrappers use consistent error handling.
195-271: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftDuplicated fail-closed transport-cloning/IP-guard logic vs.
shortcuts/doc/doc_resource_cover.go.
newDownloadTransportLeaf/configureDirectDownloadTransportare structurally near-identical tonewDocCoverTransportLeafinshortcuts/doc/doc_resource_cover.go(dial wrapping, DialTLSContext/DialTLS guards, remote-IP validation). See consolidated comment below.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/validate/url.go` around lines 195 - 271, The direct transport cloning and dial/IP-validation logic in newDownloadTransportLeaf and configureDirectDownloadTransport duplicates newDocCoverTransportLeaf; consolidate these paths through the existing shared transport-wrapper helper or extract their common implementation into one reusable symbol. Preserve fail-closed handling, DialContext/DialTLSContext/DialTLS coverage, connection closing on validation failure, and the distinct proxy behavior.shortcuts/doc/doc_resource_cover.go (1)
727-801: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftDuplicated fail-closed transport-cloning/IP-guard logic vs.
internal/validate/url.go.
blockedDocCoverTransport/cloneDocCoverTransport/newDocCoverTransportLeafclosely mirrorblockedDownloadTransport/cloneDownloadTransport/newDownloadTransportLeafininternal/validate/url.go, including the new DialTLS-guard wrapping. See consolidated comment below.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/doc/doc_resource_cover.go` around lines 727 - 801, Replace the duplicated blocked transport, clone logic, and leaf IP-guard implementation in blockedDocCoverTransport, cloneDocCoverTransport, and newDocCoverTransportLeaf with the shared transport-cloning/validation utilities from internal/validate/url.go. Reuse the existing blockedDownloadTransport, cloneDownloadTransport, and newDownloadTransportLeaf behavior, including DialTLS and DialTLSContext guarding, while preserving the document-cover fail-closed behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/cmdutil/factory_default.go`:
- Around line 97-126: The four rejection paths in safeRedirectPolicy must return
appropriately classified errs errors instead of fmt.Errorf: use validation for
redirect-count or invalid-request cases, network for transport redirect
failures, and security-policy errors for HTTPS downgrade or credential-stripping
policy violations. Preserve the existing messages and behavior, and update the
redirect assertions in factory_http_test.go to verify both the typed error and
message.
In `@internal/transport/policy_router.go`:
- Around line 56-68: Replace the bare fmt.Errorf calls with appropriate errs.*
constructors for all affected transport guards: the nil-request and
invalid-class handling in internal/transport/policy_router.go at lines 56-68 and
81-94, and the redirect-location and blocked-redirect handling in
internal/transport/default_client.go at lines 55-80. Update the relevant
RoundTrip and redirect-checking logic while preserving their existing control
flow and messages.
In `@internal/validate/url.go`:
- Around line 185-193: Replace the bare fmt.Errorf used to initialize
blockedDownloadTransport.err in the transport-cloning fallback with
errs.NewInternalError(errs.SubtypeUnknown, ...), matching the typed error
construction used by blockedDocCoverTransport. Preserve the existing "cannot
safely clone download transport %T" message and formatting arguments.
---
Outside diff comments:
In `@internal/validate/url.go`:
- Line 1: Extract the duplicated transport clone-and-guard wiring from
newDownloadTransportLeaf/configureDirectDownloadTransport and
cloneDocCoverTransport/newDocCoverTransportLeaf into one shared helper.
Parameterize it with the remote-IP validation predicate and proxy policy,
preserving frozen selected-proxy behavior for downloads and always-disabled
proxy behavior for document covers; ensure cloning failures fail closed and all
DialContext/DialTLSContext/DialTLS wrappers use consistent error handling.
- Around line 195-271: The direct transport cloning and dial/IP-validation logic
in newDownloadTransportLeaf and configureDirectDownloadTransport duplicates
newDocCoverTransportLeaf; consolidate these paths through the existing shared
transport-wrapper helper or extract their common implementation into one
reusable symbol. Preserve fail-closed handling,
DialContext/DialTLSContext/DialTLS coverage, connection closing on validation
failure, and the distinct proxy behavior.
In `@shortcuts/doc/doc_resource_cover.go`:
- Around line 727-801: Replace the duplicated blocked transport, clone logic,
and leaf IP-guard implementation in blockedDocCoverTransport,
cloneDocCoverTransport, and newDocCoverTransportLeaf with the shared
transport-cloning/validation utilities from internal/validate/url.go. Reuse the
existing blockedDownloadTransport, cloneDownloadTransport, and
newDownloadTransportLeaf behavior, including DialTLS and DialTLSContext
guarding, while preserving the document-cover fail-closed behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b8af4ea3-abfb-4bed-b614-408a8887f761
📒 Files selected for processing (34)
cmd/config/init_interactive.gocmd/doctor/doctor.goextension/transport/sidecar/interceptor_test.goextension/transport/types.gointernal/auth/transport.gointernal/cmdutil/factory.gointernal/cmdutil/factory_default.gointernal/cmdutil/factory_http_test.gointernal/cmdutil/factory_proxy_warn_test.gointernal/cmdutil/testing_test.gointernal/cmdutil/transport.gointernal/cmdutil/transport_test.gointernal/core/types.gointernal/core/types_test.gointernal/registry/remote.gointernal/transport/config.gointernal/transport/default_client.gointernal/transport/extension.gointernal/transport/extension_test.gointernal/transport/policy_router.gointernal/transport/policy_router_test.gointernal/transport/shared.gointernal/transport/shared_test.gointernal/update/update.gointernal/validate/url.gointernal/validate/url_test.goshortcuts/apps/file_common.goshortcuts/doc/doc_resource_cover.goshortcuts/doc/doc_resource_cover_test.goshortcuts/im/helpers.goshortcuts/mail/helpers.goshortcuts/mail/signature_compose.goshortcuts/minutes/minutes_download.goshortcuts/minutes/minutes_download_test.go
| func safeRedirectPolicy(req *http.Request, via []*http.Request) error { | ||
| if len(via) >= 10 { | ||
| return fmt.Errorf("too many redirects") | ||
| } | ||
| if len(via) > 0 && req.URL.Host != via[0].URL.Host { | ||
| if len(via) == 0 { | ||
| return nil | ||
| } | ||
| original := via[0] | ||
| previous := via[len(via)-1] | ||
| if previous.URL != nil && req.URL != nil && strings.EqualFold(previous.URL.Scheme, "https") && !strings.EqualFold(req.URL.Scheme, "https") { | ||
| return fmt.Errorf("redirect from HTTPS to %s is not allowed", req.URL.Scheme) | ||
| } | ||
| if !sameRedirectOrigin(previous.URL, req.URL) { | ||
| if req.Method != http.MethodGet && req.Method != http.MethodHead { | ||
| return fmt.Errorf("cross-origin redirect for HTTP method %s is not allowed", req.Method) | ||
| } | ||
| if req.Body != nil || req.GetBody != nil { | ||
| return fmt.Errorf("cross-origin redirect with a request body is not allowed") | ||
| } | ||
| } | ||
| // net/http copies initial headers onto every redirect request. Continue | ||
| // stripping credentials for every hop outside the initial origin, even when | ||
| // two consecutive redirect targets share an origin. | ||
| if !sameRedirectOrigin(original.URL, req.URL) { | ||
| req.Header.Del("Authorization") | ||
| req.Header.Del("X-Lark-MCP-UAT") | ||
| req.Header.Del("X-Lark-MCP-TAT") | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n --type=go 'func New.*Error' -g '!*_test.go' internal/errs errs 2>/dev/null
rg -n --type=go 'SubtypeNetwork|SubtypeInvalidArgument|SubtypeSecurity' -g '!*_test.go'Repository: larksuite/cli
Length of output: 1218
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate relevant files =="
git ls-files | rg '(^|/)errs/types\.go$|errs/types_test\.go$|internal/cmdutil/factory_default\.go$|factory_http_test\.go$|internal/cmdutil/' | sed -n '1,220p'
echo
echo "== factory_default imports/usages around safeRedirectPolicy =="
sed -n '1,180p' internal/cmdutil/factory_default.go
echo
echo "== errs constructors and subtype enum =="
sed -n '1,260p' errs/types.go
sed -n '380,570p' errs/types.go
sed -n '700,780p' errs/types.go
echo
echo "== existing NewSecurityPolicyError usage patterns =="
rg -n --type=go 'NewSecurityPolicyError|Security.*Redirect|HTTPS.*redirect|redirect from HTTPS|cross-origin redirect|too many redirects' .Repository: larksuite/cli
Length of output: 25333
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== factory_http_test redirect assertions =="
sed -n '1,260p' internal/cmdutil/factory_http_test.go
echo
echo "== errs subtype constants =="
sed -n '250,380p' errs/types.go
echo
echo "== validate too many redirects =="
sed -n '90,135p' internal/validate/url.go
echo
echo "== existing policy/security typed errors in transport/cmdutil =="
rg -n --type=go 'NewSecurityPolicyError|SubtypeAccessDenied|CategoryPolicy|too many redirects|HTTPS.*redirect|cross-origin redirect' internal/cmdutil internal/transport errs --glob '!*test.go' | sed -n '1,220p'
echo
echo "== imports of errs in cmdutil =="
rg -n --type=go '"github.com/larksuite/cli/errs"|errs\.' internal/cmdutil --glob '!*test.go' | sed -n '1,220p'Repository: larksuite/cli
Length of output: 18745
Return typed errs.* errors from the redirect policy.
Replace the four fmt.Errorf cases in safeRedirectPolicy with the appropriate typed error constructors (errs.NewValidationError/errs.NewNetworkError/errs.NewSecurityPolicyError depending on the policy intent). These become the command-facing failure when http.Client.CheckRedirect rejects a redirect, so the error should stay typed rather than downgrading to an unclassified message. Update the factory_http_test.go redirect assertions to keep testing the typed error shape/message.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/cmdutil/factory_default.go` around lines 97 - 126, The four
rejection paths in safeRedirectPolicy must return appropriately classified errs
errors instead of fmt.Errorf: use validation for redirect-count or
invalid-request cases, network for transport redirect failures, and
security-policy errors for HTTPS downgrade or credential-stripping policy
violations. Preserve the existing messages and behavior, and update the redirect
assertions in factory_http_test.go to verify both the typed error and message.
Source: Coding guidelines
| func (r *HTTPPolicyRouter) RoundTrip(req *http.Request) (*http.Response, error) { | ||
| if req == nil { | ||
| return nil, fmt.Errorf("HTTP policy router received a nil request") | ||
| } | ||
| class, err := classifyRequest(req) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if class == exttransport.RequestClassPlatform { | ||
| return r.platform.RoundTrip(req) | ||
| } | ||
| return r.external.RoundTrip(req) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether internal/errs (or wherever errs.* lives) imports internal/transport,
# which would create a cycle if internal/transport imports errs.
fd -t f '.go$' internal/errs 2>/dev/null | xargs grep -l 'internal/transport'
rg -n 'package errs' -g '*.go' | head -5Repository: larksuite/cli
Length of output: 151
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant file locations:"
fd -t f '(^policy_router\.go$|^default_client\.go$|^go\.mod$|errs|error)' . | sed -n '1,200p'
echo
echo "policy_router.go outline:"
ast-grep outline internal/transport/policy_router.go || true
echo
echo "default_client.go outline:"
ast-grep outline internal/transport/default_client.go || true
echo
echo "transport imports/usages of errs/error helpers:"
rg -n 'internal/(errs|output)|errs\.[A-Za-z0-9]+' internal/transport internal/errs internal -g '*.go' | sed -n '1,200p'
echo
echo "Err* definitions/constructors:"
rg -n 'func [A-Z][A-Za-z0-9_]*\(.*Error|type .*Error struct|Err[A-Za-z0-9]\s*=|BuildAPIError|Err.*Error|Err[A-Z]' internal -g '*.go' | sed -n '1,300p'Repository: larksuite/cli
Length of output: 24862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Go module package paths:"
grep -n '^module ' go.mod
sed -n '1,120p' go.mod
echo
echo "Where errs package is declared:"
fd -t f '.go$' . | xargs grep -l '^package errs$' 2>/dev/null | sed -n '1,50p'
echo
echo "errs package files in module tree:"
fd -d 4 -t f '.go$' . | xargs grep -l '^package errs$' 2>/dev/null | sort
echo
echo "transport policy_router.go:"
cat -n internal/transport/policy_router.go | sed -n '1,180p'
echo
echo "transport default_client.go:"
cat -n internal/transport/default_client.go | sed -n '1,145p'
echo
echo "errors package candidates outline/slices:"
for f in $(fd -t f '.go$' . | xargs grep -l '^package errs$' 2>/dev/null | head -10); do
echo "--- $f"
ast-grep outline "$f" || true
sed -n '1,220p' "$f"
done
echo
echo "any circular imports involving internal/errs candidates and internal/transport?"
python3 - <<'PY'
import pathlib, re
mods = []
for p in pathlib.Path('.').rglob('go.mod'):
mods.append(p.read_text(errors='ignore'))
paths = set()
for m in mods:
for line in m.splitlines():
m = re.match(r'\s*-\s*(.+?)\s*(?://.*)?\s*$', line)
if m:
paths.add(m.group(1))
print("tracked paths:", paths)
PY
echo
echo "imports that mention errs in internal packages:"
python3 - <<'PY'
import pathlib, re
for p in pathlib.Path('.').rglob('*.go'):
try:
s=p.read_text(errors='ignore')
except:
continue
lines=s.splitlines()
has_err_import=False
for i,line in enumerate(lines,1):
if re.search(r'"[^"]+errs[^"]*"', line):
print(f"{p}:{i}:{line.strip()}")
has_err_import=True
break
PYRepository: larksuite/cli
Length of output: 50369
Replace bare transport-layer errors with typed errs.* errors. These failures can become the terminal error for command HTTP calls, so the nil-request and invalid-class guards in policy_router.go, plus the redirect location/blocked-redirect guards in default_client.go, should be created with errs.* error constructors instead of fmt.Errorf.
📍 Affects 2 files
internal/transport/policy_router.go#L56-L68(this comment)internal/transport/policy_router.go#L81-L94internal/transport/default_client.go#L55-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/transport/policy_router.go` around lines 56 - 68, Replace the bare
fmt.Errorf calls with appropriate errs.* constructors for all affected transport
guards: the nil-request and invalid-class handling in
internal/transport/policy_router.go at lines 56-68 and 81-94, and the
redirect-location and blocked-redirect handling in
internal/transport/default_client.go at lines 55-80. Update the relevant
RoundTrip and redirect-checking logic while preserving their existing control
flow and messages.
Source: Coding guidelines
| if source, ok := base.(*http.Transport); ok && source != nil { | ||
| cloned := source.Clone() | ||
| rebuilt, transformed := newDownloadTransportLeaf(cloned) | ||
| if transformed && rebuilt != nil { | ||
| return rebuilt | ||
| } | ||
| } | ||
| return &blockedDownloadTransport{err: fmt.Errorf("cannot safely clone download transport %T", base)} | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use a typed errs.* error instead of bare fmt.Errorf.
blockedDownloadTransport.err is built with a bare fmt.Errorf, whereas the equivalent blockedDocCoverTransport in shortcuts/doc/doc_resource_cover.go (line 752) uses errs.NewInternalError(errs.SubtypeUnknown, ...). Once this error propagates up through callers that classify errors into typed errs.* (e.g., a network-error wrapper), a raw error here risks being misclassified as a generic transport failure instead of preserved as the specific "cannot safely clone" internal condition.
🔧 Proposed fix
- return &blockedDownloadTransport{err: fmt.Errorf("cannot safely clone download transport %T", base)}
+ return &blockedDownloadTransport{err: errs.NewInternalError(errs.SubtypeUnknown, "cannot safely clone download transport %T", base)}As per coding guidelines, "Command-facing failures must use typed errs.* errors; do not use legacy output.Err* helpers, final bare fmt.Errorf, or bare errors.New."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if source, ok := base.(*http.Transport); ok && source != nil { | |
| cloned := source.Clone() | |
| rebuilt, transformed := newDownloadTransportLeaf(cloned) | |
| if transformed && rebuilt != nil { | |
| return rebuilt | |
| } | |
| } | |
| return &blockedDownloadTransport{err: fmt.Errorf("cannot safely clone download transport %T", base)} | |
| } | |
| if source, ok := base.(*http.Transport); ok && source != nil { | |
| cloned := source.Clone() | |
| rebuilt, transformed := newDownloadTransportLeaf(cloned) | |
| if transformed && rebuilt != nil { | |
| return rebuilt | |
| } | |
| } | |
| return &blockedDownloadTransport{err: errs.NewInternalError(errs.SubtypeUnknown, "cannot safely clone download transport %T", base)} | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/validate/url.go` around lines 185 - 193, Replace the bare fmt.Errorf
used to initialize blockedDownloadTransport.err in the transport-cloning
fallback with errs.NewInternalError(errs.SubtypeUnknown, ...), matching the
typed error construction used by blockedDocCoverTransport. Preserve the existing
"cannot safely clone download transport %T" message and formatting arguments.
Source: Coding guidelines
Summary
Centralize outbound HTTP transport construction and routing so platform-owned endpoints and external destinations use explicit, consistent policies. The change preserves existing provider and factory behavior while making proxy, redirect, security-header, and download handling easier to reason about and test.
Changes
Test Plan
make buildGOFLAGS=-p=4 make unit-testgo vet ./...gofmt -l .produces no outputgo mod tidyleavesgo.modandgo.sumunchangedgo run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/mainmake sidecar-testlark-cli --versionRelated Issues
Summary by CodeRabbit