Skip to content

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

Open
JackZhao10086 wants to merge 4 commits into
mainfrom
feat/sec_mode
Open

feat: add proxy plugin mode for CLI HTTP transport#1181
JackZhao10086 wants to merge 4 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
    • Added HTTP proxy support configurable via environment variables and a per-user proxy_config.json.
    • Proxy configuration can force outbound requests through a specified loopback proxy and supports masking credentials in messages.
    • Added support for specifying a custom root CA bundle for TLS interception via a CA path.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8247f135-d23a-469c-838b-ba06ab10f0a9

📥 Commits

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

📒 Files selected for processing (2)
  • internal/proxyplugin/transport.go
  • internal/proxyplugin/transport_test.go

📝 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, integration into util SharedTransport(), 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.
Proxy util import and SharedTransport hookup
internal/util/proxy.go
Add proxyplugin import, proxyWarningOnce guard, and prefer proxyplugin.SharedTransport() inside util SharedTransport().
Configuration data model and path resolution
internal/proxyplugin/config.go
Config struct (Enable, Proxy, CAPath), ConfigFileName, and Path() to resolve on-disk config.
Configuration loading with caching and merging
internal/proxyplugin/config.go
Load() with sync.Once cache: read JSON config, build env-only config when env vars present, merge file+env with env precedence, return (nil,nil) when unset.
Configuration field assembly and parsing
internal/proxyplugin/config.go
Enabled(), loadFromEnv/applyEnvOverrides, and parseBoolEnv to parse boolean env values robustly.
Configuration validation and credential redaction
internal/proxyplugin/config.go
proxyURL() enforces http scheme, host 127.0.0.1, explicit port, no path/query/fragment; redactProxyURL() masks credentials.
Configuration application and tests
internal/proxyplugin/config.go, internal/proxyplugin/config_test.go
ApplyToTransport() clones transport, forces proxy via http.ProxyURL, applies extra CA; tests cover missing file, proxy set, host/URL validation, env-only config, and env-overrides-file.
TLS CA certificate support
internal/proxyplugin/tls_ca.go, internal/proxyplugin/tls_ca_test.go
applyExtraRootCA() validates absolute secure path, reads PEM, creates/appends to cert pool, clones/initializes TLS config, enforces TLS 1.2 min, sets RootCAs; tests cover error and success cases.
Proxy-plugin transport and tests
internal/proxyplugin/transport.go, internal/proxyplugin/transport_test.go
Lazy cached transports for enabled and fail-closed paths; SharedTransport() returns (transport, true) when plugin is enabled or errored (fail-closed cached), otherwise (nil,false); tests validate enabled, not-configured, fail-closed, and caching semantics.
Proxy test infrastructure updates
internal/util/proxy_test.go
Add unsetEnv()/unsetProxyPluginEnv() helpers and update existing proxy detection/warning/redaction tests to use temp config dirs and clear plugin env for deterministic tests.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

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

Suggested labels

size/XL, feature

Suggested reviewers

  • sang-neo03

Poem

🐰 I hop a patch to guard the net,
Loopback proxies snugly set,
Extra CA tucked in my sack,
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 'feat: add proxy plugin mode for CLI HTTP transport' clearly and specifically describes the main change: adding a new proxy plugin mode for handling CLI HTTP transport.
Description check ✅ Passed The description includes all required sections (Summary, Changes, Test Plan, Related Issues), with detailed explanations of proxy plugin functionality, config loading, environment variables, validation, unit tests, and documentation. However, manual verification remains incomplete (unchecked).
Docstring Coverage ✅ Passed Docstring coverage is 85.53% 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 73.56322% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.94%. Comparing base (d126ea2) to head (6d161b0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
internal/proxyplugin/config.go 69.36% 21 Missing and 13 partials ⚠️
internal/proxyplugin/transport.go 81.25% 3 Missing and 3 partials ⚠️
internal/proxyplugin/tls_ca.go 86.20% 2 Missing and 2 partials ⚠️
internal/util/proxy.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1181      +/-   ##
==========================================
+ Coverage   68.92%   68.94%   +0.01%     
==========================================
  Files         629      632       +3     
  Lines       58762    58936     +174     
==========================================
+ Hits        40503    40631     +128     
- Misses      14952    14979      +27     
- Partials     3307     3326      +19     

☔ 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@6d161b025358870016b7d1422342eaa12121cf10

🧩 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

- `LARKSUITE_CLI_PROXY_ADDRESS` must use the `http` scheme only.
- The host of `LARKSUITE_CLI_PROXY_ADDRESS` must be `127.0.0.1`.
- `LARKSUITE_CLI_PROXY_ADDRESS` must not contain a path.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document all rejected URL components.

The constraint states the proxy address "must not contain a path," but based on the PR objectives, the validation also rejects query and fragment components.

📝 Proposed fix to document all constraints
-- `LARKSUITE_CLI_PROXY_ADDRESS` must not contain a path.
+- `LARKSUITE_CLI_PROXY_ADDRESS` must not contain a path, query, or fragment.
📝 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.

Suggested change
- `LARKSUITE_CLI_PROXY_ADDRESS` must not contain a path.
- `LARKSUITE_CLI_PROXY_ADDRESS` must not contain a path, query, or fragment.
🤖 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/secplugin/README.md` at 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).

Comment thread internal/proxyplugin/README.zh-CN.md Outdated
Comment thread internal/proxyplugin/README.zh-CN.md Outdated

- `LARKSUITE_CLI_PROXY_ADDRESS` 只允许 `http` 协议。
- `LARKSUITE_CLI_PROXY_ADDRESS` 的 host 必须是 `127.0.0.1`。
- `LARKSUITE_CLI_PROXY_ADDRESS` 不能带路径。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document all rejected URL components.

The constraint states "不能带路径" (must not contain a path), but the validation also rejects query and fragment components per the PR objectives.

📝 Proposed fix to document all constraints
-- `LARKSUITE_CLI_PROXY_ADDRESS` 不能带路径。
+- `LARKSUITE_CLI_PROXY_ADDRESS` 不能带路径、查询参数或片段标识符。
📝 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.

Suggested change
- `LARKSUITE_CLI_PROXY_ADDRESS` 不能带路径。
- `LARKSUITE_CLI_PROXY_ADDRESS` 不能带路径、查询参数或片段标识符
🤖 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/secplugin/README.zh-CN.md` at 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.

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
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.

1 participant