Skip to content

feat: support trusted remote auth proxy#1163

Open
zhexuany wants to merge 3 commits into
larksuite:mainfrom
zhexuany:feat/remote-auth-proxy
Open

feat: support trusted remote auth proxy#1163
zhexuany wants to merge 3 commits into
larksuite:mainfrom
zhexuany:feat/remote-auth-proxy

Conversation

@zhexuany
Copy link
Copy Markdown

@zhexuany zhexuany commented May 28, 2026

Summary

Adds a trusted remote HTTPS auth proxy path for environments where lark-cli cannot receive app secrets directly.

Changes

  • Add lark-cli config auth-proxy trust|untrust|list to store trusted remote proxy hosts in local config instead of env.
  • Allow LARKSUITE_CLI_AUTH_PROXY=https://host[:port] only when the host is locally trusted, while keeping HTTP constrained to same-host sidecar mode.
  • Split remote proxy session from request signing: LARKSUITE_CLI_PROXY_SESSION is sent to the proxy, while LARKSUITE_CLI_PROXY_KEY signs requests and is not transmitted.
  • Harden proxy endpoint and X-Lark-Proxy-Target parsing, and share target validation with the demo servers.

Test Plan

  • go test ./sidecar -count=1
  • go test -tags authsidecar ./extension/credential/sidecar ./extension/transport/sidecar -count=1
  • go test -tags authsidecar_demo ./sidecar/server-demo -count=1
  • go test -tags authsidecar_multi_tenant_demo ./sidecar/server-multi-tenant-demo -count=1
  • go test ./cmd/... ./internal/... ./shortcuts/... ./extension/... -count=1
  • go test -tags authsidecar ./cmd/... ./internal/... ./shortcuts/... ./extension/... ./sidecar -count=1
  • go vet ./...
  • gofmt -l .
  • go mod tidy (no go.mod / go.sum diff)
  • git diff --check
  • go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/main
  • make unit-test blocked locally: Go go1.26.0 darwin/arm64 crashes in runtime.systemstack_switch under -race -gcflags="all=-N -l"; observed as runtime SIGSEGV before package assertions run.

Related Issues

  • None

Summary by CodeRabbit

Release Notes

  • New Features

    • Added config auth-proxy command to manage trusted remote authentication proxy hosts with trust, untrust, and list subcommands.
    • Introduced support for remote HTTPS managed authentication proxies alongside existing local sidecar mode, with trust validation to ensure secure connections.
  • Tests

    • Comprehensive test coverage for auth-proxy command functionality, configuration persistence, and remote proxy validation.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 28, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56404724-8635-4e53-84e7-867b36195a20

📥 Commits

Reviewing files that changed from the base of the PR and between 440104f and 0c980d8.

📒 Files selected for processing (1)
  • internal/envvars/envvars.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/envvars/envvars.go

📝 Walkthrough

Walkthrough

Adds remote managed HTTPS auth-proxy mode alongside local sidecar mode. Introduces config persistence for trusted proxy hosts, protocol parsing for mode-aware endpoint validation, CLI trust/untrust/list commands, credential provider and transport interceptor updates for remote proxy support, and demo server consolidation to use centralized parsing helpers.

Changes

Remote Managed Auth Proxy Support

Layer / File(s) Summary
Auth Proxy Configuration Foundation
internal/core/config.go, internal/core/config_test.go, internal/envvars/envvars.go
Extends MultiAppConfig with optional AuthProxy section containing TrustedHosts. Exports LoadAuthProxyConfig() and UpdateAuthProxyConfig(update) to manage trust decisions in the base config directory (not workspace-scoped). Adds new CliProxySession env var constant. Config tests verify behavior across missing files, app preservation, workspace isolation, and filesystem atomicity.
Wire Protocol Types and Validators
sidecar/protocol.go, sidecar/hmac_test.go
Adds ProxyMode, ProxyEndpoint, HeaderProxySession constants and types; implements ParseProxyEndpoint to classify local HTTP vs remote HTTPS endpoints with strict URL validation, reserved-host rejection, and loopback/same-host constraints. Introduces NormalizeRemoteProxyTrustHost, IsTrustedRemoteProxyHost, ParseProxyTarget helpers for trust canonicalization, matching, and safe target parsing. Refactors ValidateProxyAddr and ProxyHost to delegate to new validators. Tests cover endpoint classification, trust normalization, host matching, target safety, and removal of prior HTTPS rejection semantics.
CLI Commands for Trust Management
cmd/config/auth_proxy.go, cmd/config/auth_proxy_test.go, cmd/config/config.go
Implements config auth-proxy Cobra command with trust, untrust, list subcommands. trust normalizes host, refuses execution in agent workspaces, requires confirmation unless --yes set, updates persisted trusted hosts via core.UpdateAuthProxyConfig (idempotent), outputs JSON with trustedHost and changed. untrust removes matching hosts. list prints trusted hosts as JSON. Tests validate confirmation flow, agent-workspace rejection, idempotence, HTTP rejection, and untrust removal.
Credential Provider Mode Handling
extension/credential/sidecar/provider.go, extension/credential/sidecar/provider_test.go
ResolveAccount now parses auth-proxy endpoint and switches on proxy mode: local mode requires CliProxyKey; remote mode validates remote host trust (via requireTrustedRemoteProxy helper), then requires both CliProxyKey and CliProxySession. Loads trust config independently of workspace scope. Tests verify successful remote HTTPS resolution, untrusted-proxy rejection, and missing-env-var errors.
Transport Interceptor Remote Proxy Support
extension/transport/sidecar/interceptor.go, extension/transport/sidecar/interceptor_test.go
ResolveInterceptor now parses LARKSUITE_CLI_AUTH_PROXY endpoint, enforces remote trust, derives signing key and session per mode via proxyAuthMaterial helper, and populates interceptor with proxyScheme, proxyMode, proxySession. PreRoundTrip strips placeholder HeaderProxySession header, conditionally injects real session header for remote mode, and rewrites request URL scheme (defaulting to http). Tests validate header injection, URL rewriting, HMAC signature generation, and untrusted-proxy rejection.
Demo Server Protocol Consolidation
sidecar/server-demo/handler.go, sidecar/server-demo/handler_test.go, sidecar/server-multi-tenant-demo/handler.go, sidecar/server-multi-tenant-demo/handler_test.go, sidecar/server-demo/forward.go, sidecar/server-multi-tenant-demo/forward.go, sidecar/server-demo/README.md
Demo servers replace local parseTarget helpers with centralized sidecar.ParseProxyTarget. Both demo handlers' isProxyHeader now recognize HeaderProxySession. README updated to explicitly distinguish local http:///bare host:port mode (loopback/same-host; requires LARKSUITE_CLI_PROXY_KEY) from remote https:// managed proxy mode (requires LARKSUITE_CLI_PROXY_SESSION and LARKSUITE_CLI_PROXY_KEY), with note that demo implements only local mode. Tests updated to use sidecar.ParseProxyTarget and assert HeaderProxySession handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • larksuite/cli#532: Lays the foundation for the sidecar auth proxy plumbing (sidecar protocol/provider/interceptor paths, proxy env var parsing) that this PR extends with remote HTTPS trust/session support.
  • larksuite/cli#934: Modifies demo request-target parsing/forwarding logic that this PR consolidates by replacing demo-local parseTarget functions with the centralized sidecar.ParseProxyTarget validator.

Suggested labels

domain/base, feature

Suggested reviewers

  • liangshuo-1
  • sang-neo03
  • evandance

Poem

🐰 A rabbit hops through proxy gates,
Trusting hosts and parsing states—
Local hops stay simple, plain,
Remote paths need signing's chain.
Config saved, commands run swift,
Auth flows leap with trust's fine gift!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: support trusted remote auth proxy' accurately and concisely summarizes the primary feature added: support for trusted remote HTTPS auth proxies.
Description check ✅ Passed The PR description follows the template structure with Summary, Changes, and Test Plan sections. It clearly explains the motivation, lists major changes comprehensively, and provides an extensive test plan with completed checks.
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 unit tests (beta)
  • Create PR with unit tests

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 28, 2026
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: 0

🧹 Nitpick comments (3)
sidecar/protocol.go (1)

115-122: ⚡ Quick win

Reserved host check bypassed by non-default ports.

The reservedRemoteProxyHosts map contains hostnames without ports (e.g., "open.feishu.cn"), but isReservedRemoteProxyHost canonicalizes the authority which preserves non-default ports (e.g., "open.feishu.cn:8443"). This means https://open.feishu.cn:8443 would not be rejected.

While this edge case is unlikely in practice, the defense-in-depth intent suggests blocking these hostnames regardless of port.

Suggested fix to check hostname without port
 func isReservedRemoteProxyHost(authority string) bool {
-	host, err := canonicalHTTPSAuthority(authority)
+	u, err := url.Parse("https://" + authority)
 	if err != nil {
 		return false
 	}
-	return reservedRemoteProxyHosts[host]
+	return reservedRemoteProxyHosts[strings.ToLower(u.Hostname())]
 }

Also applies to: 363-369

🤖 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 `@sidecar/protocol.go` around lines 115 - 122, reservedRemoteProxyHosts
currently maps hostnames without ports but isReservedRemoteProxyHost performs
its lookup against the canonicalized authority which can include non-default
ports (e.g., "open.feishu.cn:8443"), allowing bypass; change the lookup to strip
any port and use only the hostname when checking reservedRemoteProxyHosts: in
isReservedRemoteProxyHost extract the hostname portion (e.g., call
urlObj.Hostname() or use net.SplitHostPort with a fallback) and then check
reservedRemoteProxyHosts[hostname], and apply the same hostname-only check to
the other reserved-host check mentioned alongside reservedRemoteProxyHosts so
ports are ignored for matching.
extension/transport/sidecar/interceptor_test.go (1)

187-196: 💤 Low value

Consider adding a test for missing PROXY_SESSION in remote mode.

The credential provider test file has TestResolveAccount_RemoteHTTPSMissingProxySession, but there's no equivalent test here for ResolveInterceptor. Adding TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession would ensure symmetric coverage between the credential and transport layers.

📝 Suggested test
func TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession(t *testing.T) {
	trustRemoteProxy(t, "auth-proxy.example.com")
	t.Setenv(envvars.CliAuthProxy, "https://auth-proxy.example.com")
	t.Setenv(envvars.CliProxyKey, "proxy-signing-key")
	t.Setenv(envvars.CliProxySession, "")

	if interceptor := (&Provider{}).ResolveInterceptor(context.Background()); interceptor != nil {
		t.Fatal("expected nil interceptor when remote proxy session is missing")
	}
}
🤖 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 `@extension/transport/sidecar/interceptor_test.go` around lines 187 - 196, Add
a symmetric test to cover the missing PROXY_SESSION case for the transport layer
by creating TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession that
mirrors TestResolveAccount_RemoteHTTPSMissingProxySession: set
trustRemoteProxy(t, "auth-proxy.example.com"), set envvars.CliAuthProxy to
"https://auth-proxy.example.com", set envvars.CliProxyKey to a non-empty value
and envvars.CliProxySession to an empty string, then assert that
(&Provider{}).ResolveInterceptor(context.Background()) returns nil (fail the
test if it is non-nil); reference the Provider type and its ResolveInterceptor
method when locating where to add the test.
extension/credential/sidecar/provider.go (1)

132-144: ⚡ Quick win

Duplicate requireTrustedRemoteProxy implementation.

This function is duplicated verbatim in extension/transport/sidecar/interceptor.go (lines 220-232). Consider extracting it to a shared location (e.g., the sidecar package or a shared internal helper) to avoid divergence and reduce maintenance burden.

♻️ Suggested refactor

Move requireTrustedRemoteProxy to a shared location, for example in the sidecar package:

// In sidecar/protocol.go or a new sidecar/trust.go
func RequireTrustedRemoteProxy(endpoint ProxyEndpoint, loadConfig func() ([]string, error)) error {
	if endpoint.Mode != ProxyModeRemote {
		return nil
	}
	trustedHosts, err := loadConfig()
	if err != nil {
		return fmt.Errorf("failed to load auth proxy trust config: %w", err)
	}
	if IsTrustedRemoteProxyHost(endpoint.Host, trustedHosts) {
		return nil
	}
	return fmt.Errorf("remote auth proxy host %q is not trusted; run `lark-cli config auth-proxy trust https://%s` outside the agent environment", endpoint.Host, endpoint.Host)
}

Then both provider.go and interceptor.go can call this shared function.

🤖 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 `@extension/credential/sidecar/provider.go` around lines 132 - 144, Extract the
duplicated requireTrustedRemoteProxy logic into a single shared function in the
sidecar package (e.g., sidecar.RequireTrustedRemoteProxy) that accepts a
sidecar.ProxyEndpoint and either calls core.LoadAuthProxyConfig internally or
accepts a loader func for testability; implement the same behavior (check
endpoint.Mode == sidecar.ProxyModeRemote, load trusted hosts, call
sidecar.IsTrustedRemoteProxyHost and return the same formatted error). Replace
both existing implementations (the requireTrustedRemoteProxy in provider.go and
the one in interceptor.go) with calls to sidecar.RequireTrustedRemoteProxy and
remove the duplicated code, updating imports accordingly.
🤖 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 `@extension/credential/sidecar/provider.go`:
- Around line 132-144: Extract the duplicated requireTrustedRemoteProxy logic
into a single shared function in the sidecar package (e.g.,
sidecar.RequireTrustedRemoteProxy) that accepts a sidecar.ProxyEndpoint and
either calls core.LoadAuthProxyConfig internally or accepts a loader func for
testability; implement the same behavior (check endpoint.Mode ==
sidecar.ProxyModeRemote, load trusted hosts, call
sidecar.IsTrustedRemoteProxyHost and return the same formatted error). Replace
both existing implementations (the requireTrustedRemoteProxy in provider.go and
the one in interceptor.go) with calls to sidecar.RequireTrustedRemoteProxy and
remove the duplicated code, updating imports accordingly.

In `@extension/transport/sidecar/interceptor_test.go`:
- Around line 187-196: Add a symmetric test to cover the missing PROXY_SESSION
case for the transport layer by creating
TestProviderResolveInterceptor_RemoteHTTPSMissingProxySession that mirrors
TestResolveAccount_RemoteHTTPSMissingProxySession: set trustRemoteProxy(t,
"auth-proxy.example.com"), set envvars.CliAuthProxy to
"https://auth-proxy.example.com", set envvars.CliProxyKey to a non-empty value
and envvars.CliProxySession to an empty string, then assert that
(&Provider{}).ResolveInterceptor(context.Background()) returns nil (fail the
test if it is non-nil); reference the Provider type and its ResolveInterceptor
method when locating where to add the test.

In `@sidecar/protocol.go`:
- Around line 115-122: reservedRemoteProxyHosts currently maps hostnames without
ports but isReservedRemoteProxyHost performs its lookup against the
canonicalized authority which can include non-default ports (e.g.,
"open.feishu.cn:8443"), allowing bypass; change the lookup to strip any port and
use only the hostname when checking reservedRemoteProxyHosts: in
isReservedRemoteProxyHost extract the hostname portion (e.g., call
urlObj.Hostname() or use net.SplitHostPort with a fallback) and then check
reservedRemoteProxyHosts[hostname], and apply the same hostname-only check to
the other reserved-host check mentioned alongside reservedRemoteProxyHosts so
ports are ignored for matching.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecda802e-d59c-4611-99f8-a628e9b71172

📥 Commits

Reviewing files that changed from the base of the PR and between a2cc5e1 and 56cacf1.

📒 Files selected for processing (19)
  • cmd/config/auth_proxy.go
  • cmd/config/auth_proxy_test.go
  • cmd/config/config.go
  • extension/credential/sidecar/provider.go
  • extension/credential/sidecar/provider_test.go
  • extension/transport/sidecar/interceptor.go
  • extension/transport/sidecar/interceptor_test.go
  • internal/core/config.go
  • internal/core/config_test.go
  • internal/envvars/envvars.go
  • sidecar/hmac_test.go
  • sidecar/protocol.go
  • sidecar/server-demo/README.md
  • sidecar/server-demo/forward.go
  • sidecar/server-demo/handler.go
  • sidecar/server-demo/handler_test.go
  • sidecar/server-multi-tenant-demo/forward.go
  • sidecar/server-multi-tenant-demo/handler.go
  • sidecar/server-multi-tenant-demo/handler_test.go

@zhexuany
Copy link
Copy Markdown
Author

zhexuany commented May 29, 2026

@liangshuo-1 PTAL

We want to manage internal user login via a single gate.

@liangshuo-1
Copy link
Copy Markdown
Collaborator

Thanks for working on this. My main concern is around the subjective / agent-safety risk.

Trusting a remote auth proxy is effectively approving a new egress path for credentials and business traffic. Once a host is trusted, subsequent requests, proxy session material, and business data can flow through it. Right now, config auth-proxy trust looks like a normal write config change and can also be run from an agent environment, so it may become “the agent approved a new remote proxy by itself” rather than “the user explicitly approved it.”

@zhexuany
Copy link
Copy Markdown
Author

Agreed. Treating auth-proxy trust as a normal config write is too weak because it grants a new egress path for credential/session material and business traffic.

I’ll change config auth-proxy trust into a human-consent boundary:

  • mark only trust as high-risk-write
  • require explicit confirmation
  • refuse trust from detected agent workspaces, with no force override
  • keep list as read and untrust as write
  • add regression tests proving an agent workspace cannot create trust, even with confirmation flags

@zhexuany
Copy link
Copy Markdown
Author

Updated the PR to make remote auth-proxy trust an explicit user authorization boundary.

config auth-proxy trust is now marked high-risk-write, requires --yes, and refuses to run from detected agent workspaces even when --yes is passed. The trust policy is stored in the user's base config, so agents can use hosts
the user already approved but cannot approve a new remote proxy themselves. The confirmation error also states that future lark-cli requests, proxy session material, and business request bodies may flow through the trusted host.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/config/auth_proxy.go (1)

77-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return typed errs.* from the remaining trust-path failures.

Line 83 and Line 100 still return legacy output.ErrValidation / output.Errorf, so this migrated cmd/config handler can emit different stderr envelopes depending on which branch fails. Please switch those branches to the same typed-error contract used by the new confirmation and agent-refusal paths.

Based on learnings: For larksuite/cli command files under cmd/**/*.go (starting with migrated commands / PR #1135 onward), RunE handlers should return typed errors directly and must not use output.ErrXxx / output.ErrWithHint.

🤖 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 `@cmd/config/auth_proxy.go` around lines 77 - 100, The two legacy output.*
returns in runConfigAuthProxyTrust must be replaced with the typed errs.*
returns used elsewhere: replace the output.ErrValidation("invalid auth proxy
host: %v", err) returned after sidecar.NormalizeRemoteProxyTrustHost with the
corresponding errs validation error (e.g., errs.Validation or the project’s
validation constructor) including the original err message, and replace the
output.Errorf(... "failed to save auth proxy trust config: %v", err) returned
from core.UpdateAuthProxyConfig with the corresponding errs internal/operational
error (e.g., errs.Internal or the project’s internal constructor) including the
original err; update the two return sites in runConfigAuthProxyTrust (the
NormalizeRemoteProxyTrustHost error path and the core.UpdateAuthProxyConfig
error path) to use those typed errs.* values so the handler consistently returns
typed errors.
🤖 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.

Outside diff comments:
In `@cmd/config/auth_proxy.go`:
- Around line 77-100: The two legacy output.* returns in runConfigAuthProxyTrust
must be replaced with the typed errs.* returns used elsewhere: replace the
output.ErrValidation("invalid auth proxy host: %v", err) returned after
sidecar.NormalizeRemoteProxyTrustHost with the corresponding errs validation
error (e.g., errs.Validation or the project’s validation constructor) including
the original err message, and replace the output.Errorf(... "failed to save auth
proxy trust config: %v", err) returned from core.UpdateAuthProxyConfig with the
corresponding errs internal/operational error (e.g., errs.Internal or the
project’s internal constructor) including the original err; update the two
return sites in runConfigAuthProxyTrust (the NormalizeRemoteProxyTrustHost error
path and the core.UpdateAuthProxyConfig error path) to use those typed errs.*
values so the handler consistently returns typed errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8bfc8e9-c7e6-457e-95b1-f547663d1db7

📥 Commits

Reviewing files that changed from the base of the PR and between 56cacf1 and 440104f.

📒 Files selected for processing (5)
  • cmd/config/auth_proxy.go
  • cmd/config/auth_proxy_test.go
  • errs/subtypes.go
  • internal/core/config.go
  • internal/core/config_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/core/config_test.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.

3 participants