Skip to content

feat: add proxy plugin mode for CLI HTTP transport#1181

Merged
albertnusouo merged 5 commits into
mainfrom
feat/sec_mode
Jun 2, 2026
Merged

feat: add proxy plugin mode for CLI HTTP transport#1181
albertnusouo merged 5 commits into
mainfrom
feat/sec_mode

Conversation

@JackZhao10086
Copy link
Copy Markdown
Collaborator

@JackZhao10086 JackZhao10086 commented May 30, 2026

Summary

Add a proxy plugin mode for CLI HTTP(S) traffic, allowing requests to be forced through a local HTTP proxy with optional extra CA trust for proxy TLS interception. This also standardizes the public config names by removing SEC wording from environment variables and renaming the config file to proxy_config.json.

Changes

  • Add internal/secplugin config loading for ~/.lark-cli/proxy_config.json / $LARKSUITE_CLI_CONFIG_DIR/proxy_config.json, with environment variable overrides.
  • Add proxy plugin environment variables: LARKSUITE_CLI_PROXY_ENABLE, LARKSUITE_CLI_PROXY_ADDRESS, and LARKSUITE_CLI_CA_PATH.
  • Apply proxy plugin settings in shared HTTP transport, forcing traffic through http://127.0.0.1:<port> when enabled.
  • Add optional extra root CA loading from an absolute PEM path for TLS inspection proxy scenarios.
  • Fail closed when proxy plugin config is invalid, instead of silently falling back to direct egress.
  • Add validation for proxy address scheme, loopback host, explicit port, path, query, and fragment.
  • Add unit tests for config loading, env overrides, proxy validation, CA handling, transport behavior, and proxy warning behavior.
  • Add English and Chinese README documentation for proxy plugin configuration and usage.

Test Plan

  • Unit tests pass: go test ./internal/envvars ./internal/secplugin ./internal/util
  • Formatting check passes: gofmt -l internal/envvars/envvars.go internal/secplugin/config.go internal/secplugin/tls_ca.go internal/secplugin/config_test.go internal/secplugin/tls_ca_test.go internal/util/proxy.go internal/util/proxy_test.go
  • Verified old names have no residual references: LARKSUITE_CLI_SEC_*, CliSec*, sec_config.json
  • Manual local verification confirms the lark-cli <domain> <command> flow works as expected

Related Issues

Summary by CodeRabbit

  • New Features

    • HTTP proxy support via environment variables and a per-user proxy_config.json.
    • Proxy mode can enforce a loopback proxy and accept a custom root CA for TLS interception.
    • Client operations (interactive setup, diagnostics, remote fetch) now use a proxy-aware HTTP client.
  • Bug Fixes / UX

    • Proxy-related warnings updated to reflect plugin mode and redact credentials.
    • Proxy config loading enforces secure file permissions and rejects unsafe configs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a proxy-plugin subsystem: config model and cached loader (file + env with env precedence), strict loopback-proxy validation and credential redaction, optional extra CA injection into TLS, lazy fail-closed transport construction, util integration (SharedTransport/NewHTTPClient), and deterministic tests.

Changes

Proxy Plugin Configuration and Transport Integration

Layer / File(s) Summary
Environment variable constants
internal/envvars/envvars.go
Three new proxy-plugin env keys: CliProxyEnable, CliProxyAddress, CliCAPath.
Config model, path, and caching
internal/proxyplugin/config.go
Config struct, ConfigFileName, Path(), and package caching for Load().
Env parsing & overrides
internal/proxyplugin/config.go
Enabled(), loadFromEnv, applyEnvOverrides, and parseBoolEnv to normalize boolean env values and merge env overrides.
Proxy URL validation & redaction
internal/proxyplugin/config.go
proxyURL() enforces http scheme, loopback 127.0.0.1, explicit port, and no path/query/fragment; redactProxyURL() masks credentials.
Apply config to transport
internal/proxyplugin/config.go, internal/proxyplugin/tls_ca.go
ApplyToTransport() clones transport, forces fixed proxy via http.ProxyURL, and applyExtraRootCA() wires an extra trusted CA, enforcing TLS >=1.2.
TLS CA tests and helper
internal/proxyplugin/tls_ca_test.go
Helper to emit test CA PEM and tests for path/permission/PEM validation, TLSClientConfig init/cloning, and MinVersion preservation.
Config tests & security checks
internal/proxyplugin/config_test.go
Deterministic helpers and tests for missing file, valid loopback proxy, non-loopback rejection, unsupported URL parts, env-only and env-overrides-file behaviors, credential redaction, and secure-file-permission checks.
Proxy-plugin transport factories
internal/proxyplugin/transport.go
Lazy cached transports: enabled proxyPluginTransport and cached fail-closed cachedBlockedTransport; SharedTransport() returns (transport,true) when plugin active or errored (fail-closed), otherwise (nil,false).
Transport tests & reset helpers
internal/proxyplugin/transport_test.go
Reset helpers and tests covering not-configured, enabled fixed-proxy behavior, invalid-config fail-closed with non-transport default, cached blocked transport semantics, and concrete blocked-transport invariants.
util integration & NewHTTPClient
internal/util/proxy.go, internal/util/proxy_test.go
Prefer proxyplugin.SharedTransport() in util SharedTransport(), update WarnIfProxied() for plugin mode, add NewHTTPClient(timeout) and plugin-aware tests plus deterministic env helpers.
Command wiring to shared client
cmd/config/init_interactive.go, cmd/doctor/doctor.go, internal/registry/remote.go
Replace direct http.Client creations with util.NewHTTPClient(...) so app flows and probes use the proxy-plugin-aware transport.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • larksuite/cli#247: Prior work modifying internal/util/proxy.go proxy detection and transport plumbing that this PR extends by integrating internal/proxyplugin.

Suggested labels

size/XL, feature

Suggested reviewers

  • liangshuo-1
  • sang-neo03

Poem

🐰 I hop a patch to guard the net,
Loopback proxies snugly set,
Extra CA tucked in my pack,
Fail‑closed shields keep traffic back—
Tests snug, envs clean, no threat.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding proxy plugin mode for CLI HTTP transport, which is the core feature across all modified files.
Description check ✅ Passed The description covers all required template sections: Summary, Changes, Test Plan, and Related Issues. However, the Test Plan has an incomplete manual verification checkbox.
Docstring Coverage ✅ Passed Docstring coverage is 88.42% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sec_mode

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label May 30, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

Codecov Report

❌ Patch coverage is 75.24272% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.19%. Comparing base (d126ea2) to head (7213eee).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
internal/proxyplugin/config.go 74.78% 19 Missing and 11 partials ⚠️
internal/proxyplugin/transport.go 73.52% 5 Missing and 4 partials ⚠️
internal/proxyplugin/tls_ca.go 80.64% 3 Missing and 3 partials ⚠️
internal/util/proxy.go 83.33% 2 Missing and 1 partial ⚠️
cmd/config/init_interactive.go 0.00% 2 Missing ⚠️
cmd/doctor/doctor.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1181      +/-   ##
==========================================
+ Coverage   68.92%   69.19%   +0.26%     
==========================================
  Files         629      633       +4     
  Lines       58762    59582     +820     
==========================================
+ Hits        40503    41229     +726     
- Misses      14952    15025      +73     
- Partials     3307     3328      +21     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 30, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@7213eeea4a09648a9fe0380a90758bee1907b70e

🧩 Skill update

npx skills add larksuite/cli#feat/sec_mode -y -g

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 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/secplugin/config.go`:
- Around line 196-198: The URL path check currently allows a trailing slash
(u.Path == "/"); update the validation to reject any path, query, or fragment
components. Change the condition that uses u.Path (and the error using
envvars.CliProxyAddress and redacted) so it returns an error when u.Path != ""
(not just when it's not "/"), and also add explicit checks to error when
u.RawQuery != "" or u.Fragment != "" to ensure no path/query/fragment are
allowed.

In `@internal/secplugin/README.md`:
- Line 76: The README currently states that LARKSUITE_CLI_PROXY_ADDRESS "must
not contain a path" but the validation also rejects query and fragment
components; update the documentation for the LARKSUITE_CLI_PROXY_ADDRESS
environment variable to explicitly list all rejected URL components (path,
query, and fragment) so it matches the actual validation behavior (refer to the
LARKSUITE_CLI_PROXY_ADDRESS symbol in the README and any related
validateProxyAddress or proxy address validation logic to ensure wording is
consistent).

In `@internal/secplugin/README.zh-CN.md`:
- Line 72: Update the README entry for LARKSUITE_CLI_PROXY_ADDRESS to list all
rejected URL components: explicitly state it must be a host[:port] (no path, no
query string, no fragment) and clarify any allowed schemes or default behavior;
reference the README line mentioning `LARKSUITE_CLI_PROXY_ADDRESS` and replace
the single "不能带路径" note with a concise bullet or sentence enumerating
"不能带路径、查询参数(query)及片段(fragment)" so the documentation matches the validation
behavior.
- Line 66: Remove the extra space in the README sentence so "设置了 代理相关环境变量"
becomes "设置了代理相关环境变量" in internal/secplugin/README.zh-CN.md; locate the line
containing the phrase "如果没有配置文件,但设置了 代理相关环境变量,也可以正常工作。" and delete the space
between "设置了" and "代理相关环境变量".

In `@internal/secplugin/tls_ca.go`:
- Around line 42-49: The code unconditionally sets t.TLSClientConfig.MinVersion
= tls.VersionTLS12 which can downgrade a cloned higher minimum (e.g.,
tls.VersionTLS13); update the logic in the TLSClientConfig setup (around
t.TLSClientConfig, Clone(), and MinVersion) to only set MinVersion when it is
zero or less than tls.VersionTLS12 (i.e., raise the floor to TLS1.2 but never
lower an existing higher MinVersion).
🪄 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

Run ID: 7ec8f6af-8a51-4d07-af51-4b2668a6de0c

📥 Commits

Reviewing files that changed from the base of the PR and between b1ecf2d and b073963.

📒 Files selected for processing (9)
  • internal/envvars/envvars.go
  • internal/secplugin/README.md
  • internal/secplugin/README.zh-CN.md
  • internal/secplugin/config.go
  • internal/secplugin/config_test.go
  • internal/secplugin/tls_ca.go
  • internal/secplugin/tls_ca_test.go
  • internal/util/proxy.go
  • internal/util/proxy_test.go

Comment thread internal/proxyplugin/config.go Outdated
Comment thread internal/proxyplugin/README.md Outdated
Comment thread internal/proxyplugin/README.zh-CN.md Outdated
Comment thread internal/proxyplugin/README.zh-CN.md Outdated
Comment thread internal/proxyplugin/tls_ca.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
internal/proxyplugin/config.go (1)

196-198: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject trailing slash in the proxy URL.

The current check allows http://127.0.0.1:3128/, which includes a path component. The documented contract is to reject any path/query/fragment. Tighten the validation to align with the fail-closed design.

🔧 Proposed fix
-	if u.Path != "" && u.Path != "/" {
+	if u.Path != "" {
 		return nil, fmt.Errorf("invalid %s %q: path is not allowed", envvars.CliProxyAddress, redacted)
 	}
🤖 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/proxyplugin/config.go` around lines 196 - 198, The URL validation
currently only rejects non-empty paths except "/" but must reject any path,
query, or fragment; update the check in internal/proxyplugin/config.go to return
an error when u.Path != "" || u.RawQuery != "" || u.Fragment != "" (preserving
the existing error message that uses envvars.CliProxyAddress and redacted) so
any URL with a path, query, or fragment is rejected per the documented contract.
🧹 Nitpick comments (1)
internal/proxyplugin/config_test.go (1)

53-58: 💤 Low value

Consolidate the repeated cache-reset boilerplate.

The three-line reset of loadOnce/loadCfg/loadErr is duplicated across all five tests here. A resetProxyPluginState() helper already exists in transport_test.go within this same package and resets exactly these globals (plus proxyPluginTransport, which is harmless to reset here). Reusing it removes the duplication.

♻️ Example for one test; apply across the others
 func TestLoad_MissingFileReturnsNil(t *testing.T) {
 	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
-	loadOnce = sync.Once{}
-	loadCfg = nil
-	loadErr = nil
-	unsetProxyPluginEnv(t)
+	unsetProxyPluginEnv(t)
+	resetProxyPluginState()

Note: sync would no longer be needed in this file after the change.

🤖 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/proxyplugin/config_test.go` around lines 53 - 58, Replace the
duplicated three-line cache reset in each test (resetting loadOnce, loadCfg,
loadErr) with a call to the existing helper resetProxyPluginState() (found in
transport_test.go) and keep the per-test unsetProxyPluginEnv(t) calls; update
tests like TestLoad_MissingFileReturnsNil to call resetProxyPluginState()
instead of manually reassigning loadOnce/loadCfg/loadErr, and remove the
now-unneeded sync import from this file.
🤖 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/proxyplugin/config.go`:
- Line 236: applyExtraRootCA currently accepts CAPath from an untrusted env var
and calls vfs.ReadFile(caPath) after trimming/IsAbs checks, allowing arbitrary
absolute-file reads; update the function (in internal/proxyplugin/tls_ca.go) to
validate the incoming caPath with validate.SafeInputPath (or an equivalent
safe-path validator for env-provided absolute paths) before calling
vfs.ReadFile, returning an error if validation fails and only proceeding to read
and parse the CA when the path is deemed safe.

In `@internal/proxyplugin/transport.go`:
- Around line 38-53: The fail-closed branches in SharedTransport and
buildProxyPluginTransport currently return http.DefaultTransport or an empty
&http.Transport{} when http.DefaultTransport is not a *http.Transport, which
allows direct egress; instead create and return a type-agnostic erroring
RoundTripper (e.g. blockedRoundTripper with an err field and RoundTrip that
returns nil, err) and use that in the !ok branch in SharedTransport (where
Load() fails) and the corresponding guard in buildProxyPluginTransport so both
functions always return a failing RoundTripper (call blockedTransport or replace
its use with blockedRoundTripper instances carrying a descriptive error) to
preserve the fail-closed contract regardless of the concrete
http.DefaultTransport type.

---

Duplicate comments:
In `@internal/proxyplugin/config.go`:
- Around line 196-198: The URL validation currently only rejects non-empty paths
except "/" but must reject any path, query, or fragment; update the check in
internal/proxyplugin/config.go to return an error when u.Path != "" ||
u.RawQuery != "" || u.Fragment != "" (preserving the existing error message that
uses envvars.CliProxyAddress and redacted) so any URL with a path, query, or
fragment is rejected per the documented contract.

---

Nitpick comments:
In `@internal/proxyplugin/config_test.go`:
- Around line 53-58: Replace the duplicated three-line cache reset in each test
(resetting loadOnce, loadCfg, loadErr) with a call to the existing helper
resetProxyPluginState() (found in transport_test.go) and keep the per-test
unsetProxyPluginEnv(t) calls; update tests like TestLoad_MissingFileReturnsNil
to call resetProxyPluginState() instead of manually reassigning
loadOnce/loadCfg/loadErr, and remove the now-unneeded sync import from this
file.
🪄 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

Run ID: de25f8b7-2579-4ed0-827e-4e38dce62d04

📥 Commits

Reviewing files that changed from the base of the PR and between b073963 and e14bf47.

📒 Files selected for processing (11)
  • internal/envvars/envvars.go
  • internal/proxyplugin/README.md
  • internal/proxyplugin/README.zh-CN.md
  • internal/proxyplugin/config.go
  • internal/proxyplugin/config_test.go
  • internal/proxyplugin/tls_ca.go
  • internal/proxyplugin/tls_ca_test.go
  • internal/proxyplugin/transport.go
  • internal/proxyplugin/transport_test.go
  • internal/util/proxy.go
  • internal/util/proxy_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/proxyplugin/README.md
  • internal/proxyplugin/README.zh-CN.md
  • internal/envvars/envvars.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/util/proxy_test.go

Comment thread internal/proxyplugin/config.go
Comment thread internal/proxyplugin/transport.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/proxyplugin/config_test.go (1)

271-276: 💤 Low value

Consider backfilling earlier tests to use resetLoadState().

The new helper consolidates the three-line reset pattern (loadOnce = sync.Once{}, loadCfg = nil, loadErr = nil) that earlier tests (lines 56-58, 75-77, 115-117, etc.) duplicate inline. Updating them to use resetLoadState() would improve consistency and reduce maintenance surface.

🤖 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/proxyplugin/config_test.go` around lines 271 - 276, Several earlier
tests duplicate the three-line cache reset pattern; replace those inline resets
(the sequence `loadOnce = sync.Once{}`, `loadCfg = nil`, `loadErr = nil`) with a
call to the new helper resetLoadState() to consolidate behavior. Search test
files for occurrences of that exact three-line pattern (examples near the tests
that currently set loadOnce/loadCfg/loadErr) and replace each block with a
single call to resetLoadState(); ensure tests still import the same package
scope so resetLoadState() is callable from those test functions.
🤖 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.

Nitpick comments:
In `@internal/proxyplugin/config_test.go`:
- Around line 271-276: Several earlier tests duplicate the three-line cache
reset pattern; replace those inline resets (the sequence `loadOnce =
sync.Once{}`, `loadCfg = nil`, `loadErr = nil`) with a call to the new helper
resetLoadState() to consolidate behavior. Search test files for occurrences of
that exact three-line pattern (examples near the tests that currently set
loadOnce/loadCfg/loadErr) and replace each block with a single call to
resetLoadState(); ensure tests still import the same package scope so
resetLoadState() is callable from those test functions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a74602ff-809f-4d93-8953-7bfea52acc1b

📥 Commits

Reviewing files that changed from the base of the PR and between 6d161b0 and 7213eee.

📒 Files selected for processing (10)
  • cmd/config/init_interactive.go
  • cmd/doctor/doctor.go
  • internal/proxyplugin/config.go
  • internal/proxyplugin/config_test.go
  • internal/proxyplugin/tls_ca.go
  • internal/proxyplugin/transport.go
  • internal/proxyplugin/transport_test.go
  • internal/registry/remote.go
  • internal/util/proxy.go
  • internal/util/proxy_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/proxyplugin/tls_ca.go
  • internal/proxyplugin/transport_test.go
  • internal/proxyplugin/config.go

@albertnusouo albertnusouo merged commit f65712c into main Jun 2, 2026
21 checks passed
@albertnusouo albertnusouo deleted the feat/sec_mode branch June 2, 2026 02:57
liangshuo-1 added a commit that referenced this pull request Jun 2, 2026
Resolve internal/registry/remote.go import conflict by keeping BOTH sides:
the typed meta model (this branch) and the proxy-aware transport client
(main #1181/#1213). The changes are orthogonal — fetchRemoteMerged now uses
transport.NewHTTPClient(fetchTimeout) over the typed MergedRegistry. Only the
import block conflicted; the transport client line auto-merged.

Change-Id: Ia61ab32c39e5b4eadb05e7efe46ff0c8dcfce568
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants