Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/cmdutil/factory_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@

func cachedHttpClientFunc(f *Factory) func() (*http.Client, error) {
return sync.OnceValues(func() (*http.Client, error) {
transport.WarnIfProxied(f.IOStreams.ErrOut)
transport.WarnIfProxied(f.IOStreams.ErrOut, f.IOStreams.IsTerminal)

var rt http.RoundTripper = transport.Shared()
rt = &RetryTransport{Base: rt}
Expand All @@ -129,7 +129,7 @@
lark.WithLogLevel(larkcore.LogLevelError),
lark.WithHeaders(BaseSecurityHeaders()),
}
transport.WarnIfProxied(f.IOStreams.ErrOut)
transport.WarnIfProxied(f.IOStreams.ErrOut, f.IOStreams.IsTerminal)

Check warning on line 132 in internal/cmdutil/factory_default.go

View check run for this annotation

Codecov / codecov/patch

internal/cmdutil/factory_default.go#L132

Added line #L132 was not covered by tests
opts = append(opts, lark.WithHttpClient(&http.Client{
Transport: buildSDKTransport(),
CheckRedirect: safeRedirectPolicy,
Expand Down
13 changes: 12 additions & 1 deletion internal/transport/warn.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,18 @@ func redactProxyURL(raw string) string {
// WarnIfProxied prints a one-time warning to w when a proxy environment variable
// is detected and proxy is not disabled via LARK_CLI_NO_PROXY. Proxy credentials
// are redacted. Safe to call multiple times; only the first call prints.
func WarnIfProxied(w io.Writer) {
//
// The warning is suppressed entirely when interactive is false — i.e. stdin is
// not a TTY, which is the case for agent / CI / piped invocations. Those callers
// frequently parse the CLI's stdout as JSON and merge streams with `2>&1`; a
// stray stderr warning then corrupts the parsed payload. Suppressing in the
// non-interactive case keeps machine-consumed output clean, while human
// interactive sessions still get the security notice. Passing interactive=false
// does not consume the once guard, so a later interactive call can still warn.
func WarnIfProxied(w io.Writer, interactive bool) {
if !interactive {
return
}
proxyWarningOnce.Do(func() {
// Proxy plugin mode overrides env proxies and LARK_CLI_NO_PROXY (see
// Shared), so its warning and disable instructions take precedence.
Expand Down
35 changes: 26 additions & 9 deletions internal/transport/warn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestWarnIfProxied_WithProxy(t *testing.T) {
t.Setenv("HTTPS_PROXY", "http://corp-proxy:3128")

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)

out := buf.String()
if out == "" {
Expand All @@ -70,13 +70,30 @@ func TestWarnIfProxied_WithoutProxy(t *testing.T) {
}

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)

if buf.Len() != 0 {
t.Errorf("expected no output when no proxy is set, got: %s", buf.String())
}
}

func TestWarnIfProxied_SilentWhenNonInteractive(t *testing.T) {
proxyWarningOnce = sync.Once{}

// Non-interactive (interactive=false) mirrors agent / CI / piped invocations
// where stdin is not a TTY. The proxy warning must be suppressed so callers
// that parse stdout as JSON — often merging streams with `2>&1` — are not
// corrupted by a stray stderr line.
t.Setenv("HTTPS_PROXY", "http://corp-proxy:3128")

var buf bytes.Buffer
WarnIfProxied(&buf, false)

if buf.Len() != 0 {
t.Errorf("expected no warning in non-interactive mode, got: %s", buf.String())
}
}

// TestWarnIfProxied_SilentWhenDisabled verifies that LARK_CLI_NO_PROXY suppresses warnings.
func TestWarnIfProxied_SilentWhenDisabled(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
Expand All @@ -88,7 +105,7 @@ func TestWarnIfProxied_SilentWhenDisabled(t *testing.T) {
t.Setenv(EnvNoProxy, "1")

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)

if buf.Len() != 0 {
t.Errorf("expected no warning when proxy is disabled, got: %s", buf.String())
Expand All @@ -105,10 +122,10 @@ func TestWarnIfProxied_OnlyOnce(t *testing.T) {
t.Setenv("HTTP_PROXY", "http://proxy:1234")

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)
first := buf.String()

WarnIfProxied(&buf)
WarnIfProxied(&buf, true)
second := buf.String()

if first == "" {
Expand Down Expand Up @@ -137,7 +154,7 @@ func TestWarnIfProxied_ProxyPluginEnabled(t *testing.T) {
t.Setenv(EnvNoProxy, "1")

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)
out := buf.String()

if !strings.Contains(out, "127.0.0.1:3128") {
Expand Down Expand Up @@ -169,7 +186,7 @@ func TestWarnIfProxied_ProxyPluginCustomCAWarns(t *testing.T) {
t.Cleanup(func() { proxyPluginStatus = old })

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)
out := buf.String()

if !strings.Contains(out, "custom CA") {
Expand All @@ -195,7 +212,7 @@ func TestWarnIfProxied_ProxyPluginEnabledRedactsCredentials(t *testing.T) {
t.Cleanup(func() { proxyPluginStatus = old })

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)
out := buf.String()

if strings.Contains(out, "s3cret") {
Expand Down Expand Up @@ -243,7 +260,7 @@ func TestWarnIfProxied_RedactsCredentials(t *testing.T) {
t.Setenv("HTTPS_PROXY", "http://admin:s3cret@proxy:8080")

var buf bytes.Buffer
WarnIfProxied(&buf)
WarnIfProxied(&buf, true)

out := buf.String()
if bytes.Contains([]byte(out), []byte("s3cret")) {
Expand Down
Loading