Skip to content

feat(core): allow overriding endpoint base URLs via env vars#1069

Open
wgzesg wants to merge 2 commits into
larksuite:mainfrom
wgzesg:feat/configurable-endpoint-base-urls
Open

feat(core): allow overriding endpoint base URLs via env vars#1069
wgzesg wants to merge 2 commits into
larksuite:mainfrom
wgzesg:feat/configurable-endpoint-base-urls

Conversation

@wgzesg
Copy link
Copy Markdown

@wgzesg wgzesg commented May 25, 2026

Add four optional environment variables that override the brand-default hosts returned by core.ResolveEndpoints, regardless of brand:

LARKSUITE_CLI_OPEN_BASE_URL
LARKSUITE_CLI_ACCOUNTS_BASE_URL
LARKSUITE_CLI_MCP_BASE_URL
LARKSUITE_CLI_APPLINK_BASE_URL

Values are trimmed and have trailing slashes stripped; empty or whitespace-only values are ignored. Every existing call site already routes through ResolveEndpoints, so OAuth device flow, app registration, console scope links, consumer URLs, doctor checks, and credential resolution all pick up the override automatically.

Public repo ships only the override mechanism — no internal hostnames.

Summary

Changes

  • Change 1
  • Change 2

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark xxx command works as expected

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Service endpoint base URLs can be overridden via environment variables for all supported services.
    • Overrides are normalized (trimmed, trailing slashes removed); whitespace-only values are ignored.
    • Invalid override values trigger a warning and fall back to brand defaults. Path-prefixed and non-HTTPS HTTP overrides are accepted.
  • Tests

    • Added comprehensive tests covering full, partial, empty, invalid, path-prefixed, and HTTP override behaviors.

Review Change Stack

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 25, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

Adds exported env-var constants and helpers to let ResolveEndpoints apply per-field environment overrides for service base URLs, normalizing and validating inputs; includes tests for full/partial overrides, empty/invalid inputs (with warnings), path prefixes, and http scheme acceptance.

Changes

Endpoint Configuration via Environment Variables

Layer / File(s) Summary
Endpoint override implementation and helpers
internal/core/types.go
Adds os and strings imports; defines EnvOpenBaseURL, EnvAccountsBaseURL, EnvMCPBaseURL, EnvAppLinkBaseURL constants and an unexported endpointEnvWarner. Refactors ResolveEndpoints to compute brand defaults then apply per-field overrides via applyEndpointEnvOverrides, and adds readEndpointEnv, isValidBaseURL, and normalizeBaseURL.
Environment override test coverage
internal/core/types_test.go
Adds six tests asserting: full-endpoint overrides with trimming/trailing-slash removal; partial overrides keep brand defaults; whitespace-only env ignored; invalid env values fall back and trigger endpointEnvWarner; path-prefixed overrides accepted; and http:// scheme accepted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudge the CLI to bend its routes anew,
Env whispers trimmed, slashes bid adieu.
Defaults stand ready when inputs go astray,
A soft warning hops out to show the way.
Tests applaud the dance — hop, validate, and play.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides detailed information about the feature but leaves template sections incomplete (Changes and Test Plan sections are unfilled placeholder text). Complete the Changes and Test Plan sections by replacing placeholder text with specific details about the implementation and verification status.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding environment variable support for overriding endpoint base URLs.
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/M Single-domain feat or fix with limited business impact label May 25, 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: 1

🧹 Nitpick comments (1)
internal/core/types_test.go (1)

56-102: ⚡ Quick win

Add malformed-override coverage to lock expected behavior.

These tests don’t yet cover invalid non-absolute override values. Adding cases like "foo" and "https://" (expecting defaults to remain unchanged) will prevent regressions in endpoint safety/robustness.

🤖 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/core/types_test.go` around lines 56 - 102, Add tests that verify
ResolveEndpoints ignores malformed non-absolute env overrides by extending or
adding to TestResolveEndpoints_EnvOverride* cases: set EnvOpenBaseURL (and/or
other Env* variables) to invalid values like "foo" and "https://" and assert the
returned endpoints from ResolveEndpoints(BrandFeishu) and
ResolveEndpoints(BrandLark) remain the respective defaults; use the existing
test names (TestResolveEndpoints_EnvOverride,
TestResolveEndpoints_EnvOverride_PartialKeepsBrandDefaults,
TestResolveEndpoints_EnvOverride_EmptyIgnored) and reference ResolveEndpoints,
EnvOpenBaseURL, EnvAccountsBaseURL, EnvMCPBaseURL, and EnvAppLinkBaseURL to
locate where to add these malformed-value assertions.
🤖 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/core/types.go`:
- Around line 76-99: applyEndpointEnvOverrides currently accepts any non-empty
trimmed value; change it to validate that the normalized value is an absolute
URL before assigning into Endpoints (Open, Accounts, MCP, AppLink). For each env
var processed in applyEndpointEnvOverrides, call normalizeBaseURL then parse the
result with url.Parse (or net/url) and ensure parsed.Scheme and parsed.Host are
non-empty (and optionally reject non-http/https schemes); only assign to ep
fields when the URL is valid, otherwise leave the existing value unchanged (or
log a warning). Keep normalizeBaseURL behavior (trim + strip trailing slashes)
and reference the symbols applyEndpointEnvOverrides, normalizeBaseURL, and the
Endpoints fields (Open, Accounts, MCP, AppLink) when making the change.

---

Nitpick comments:
In `@internal/core/types_test.go`:
- Around line 56-102: Add tests that verify ResolveEndpoints ignores malformed
non-absolute env overrides by extending or adding to
TestResolveEndpoints_EnvOverride* cases: set EnvOpenBaseURL (and/or other Env*
variables) to invalid values like "foo" and "https://" and assert the returned
endpoints from ResolveEndpoints(BrandFeishu) and ResolveEndpoints(BrandLark)
remain the respective defaults; use the existing test names
(TestResolveEndpoints_EnvOverride,
TestResolveEndpoints_EnvOverride_PartialKeepsBrandDefaults,
TestResolveEndpoints_EnvOverride_EmptyIgnored) and reference ResolveEndpoints,
EnvOpenBaseURL, EnvAccountsBaseURL, EnvMCPBaseURL, and EnvAppLinkBaseURL to
locate where to add these malformed-value assertions.
🪄 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: 931d6023-10ff-4d4e-a370-26d02011e946

📥 Commits

Reviewing files that changed from the base of the PR and between f2a4c95 and eba80bd.

📒 Files selected for processing (2)
  • internal/core/types.go
  • internal/core/types_test.go

Comment thread internal/core/types.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: 1

🤖 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/core/types.go`:
- Around line 116-127: isValidBaseURL currently treats inputs like "http://:80"
as valid and allows query/fragment parts; update it to use u.Hostname() to
ensure a non-empty hostname (reject hostless ":port"), and also reject any URL
that has non-empty u.RawQuery or u.Fragment. Keep the existing checks for
non-empty input, successful url.Parse, and scheme being "http" or "https", and
return false if any of these new conditions fail.
🪄 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: ad2b12ac-335e-4915-af66-0ee8ff3aead7

📥 Commits

Reviewing files that changed from the base of the PR and between eba80bd and f978eb7.

📒 Files selected for processing (2)
  • internal/core/types.go
  • internal/core/types_test.go

Comment thread internal/core/types.go
Comment on lines +116 to +127
// isValidBaseURL reports whether v is an absolute http/https URL with a host.
// A non-empty path is allowed (some deployments serve the API under a prefix).
func isValidBaseURL(v string) bool {
if v == "" {
return false
}
u, err := url.Parse(v)
if err != nil || u.Host == "" {
return false
}
return u.Scheme == "https" || u.Scheme == "http"
}
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Go's net/url package, what are the parsed fields (Scheme, Host, Hostname, RawQuery, Fragment) for these inputs: "http://:80", "https://example.com/base?x=1", and "https://example.com/base#frag"? Does URL.Hostname() return empty for ":80" hosts?

💡 Result:

For each input, assume you parse with url.Parse (net/url). The parsed fields you asked about are taken directly from the resulting url.URL struct: Scheme, Host, Hostname (via method), RawQuery, Fragment. The Hostname method is documented as returning u.Host with any valid port stripped, and it uses splitHostPort on u.Host [1][2]. 1) Input: "http://:80" - Scheme: "http" [1] - Host: ":80" [1][2] - Hostname: "" (empty) [2] - RawQuery: "" [1] - Fragment: "" [1] Does URL.Hostname return empty for ":80" hosts? Yes. For u.Host = ":80", splitHostPort finds the last ':' and if the trailing port is valid numeric, it splits host (prefix before ':') which is empty, and returns that empty host; the method then returns it [2]. This means Hostname is empty for the host string ":80". 2) Input: "https://example.com/base?x=1" - Scheme: "https" [1] - Host: "example.com" [1] - Hostname: "example.com" [1][2] - RawQuery: "x=1" [1] - Fragment: "" [1] 3) Input: "https://example.com/base#frag" - Scheme: "https" [1] - Host: "example.com" [1] - Hostname: "example.com" [1][2] - RawQuery: "" [1] - Fragment: "frag" [1]

Citations:


Tighten base-URL validation to reject hostless “:port” and query/fragment values.

net/url keeps u.Host as ":80" for http://:80, but u.Hostname() is empty—so the current check (u.Host == "") incorrectly treats it as valid. The current code also allows bases containing ? / #, since it doesn’t reject RawQuery or Fragment.

🔧 Proposed fix
 func isValidBaseURL(v string) bool {
 	if v == "" {
 		return false
 	}
 	u, err := url.Parse(v)
-	if err != nil || u.Host == "" {
+	if err != nil || u.Host == "" || u.Hostname() == "" {
 		return false
 	}
-	return u.Scheme == "https" || u.Scheme == "http"
+	if u.RawQuery != "" || u.Fragment != "" {
+		return false
+	}
+	return u.Scheme == "https" || u.Scheme == "http"
 }
📝 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
// isValidBaseURL reports whether v is an absolute http/https URL with a host.
// A non-empty path is allowed (some deployments serve the API under a prefix).
func isValidBaseURL(v string) bool {
if v == "" {
return false
}
u, err := url.Parse(v)
if err != nil || u.Host == "" {
return false
}
return u.Scheme == "https" || u.Scheme == "http"
}
// isValidBaseURL reports whether v is an absolute http/https URL with a host.
// A non-empty path is allowed (some deployments serve the API under a prefix).
func isValidBaseURL(v string) bool {
if v == "" {
return false
}
u, err := url.Parse(v)
if err != nil || u.Host == "" || u.Hostname() == "" {
return false
}
if u.RawQuery != "" || u.Fragment != "" {
return false
}
return u.Scheme == "https" || u.Scheme == "http"
}
🤖 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/core/types.go` around lines 116 - 127, isValidBaseURL currently
treats inputs like "http://:80" as valid and allows query/fragment parts; update
it to use u.Hostname() to ensure a non-empty hostname (reject hostless ":port"),
and also reject any URL that has non-empty u.RawQuery or u.Fragment. Keep the
existing checks for non-empty input, successful url.Parse, and scheme being
"http" or "https", and return false if any of these new conditions fail.

wgzesg and others added 2 commits May 25, 2026 16:19
Add four optional environment variables that override the brand-default
hosts returned by core.ResolveEndpoints, regardless of brand:

  LARKSUITE_CLI_OPEN_BASE_URL
  LARKSUITE_CLI_ACCOUNTS_BASE_URL
  LARKSUITE_CLI_MCP_BASE_URL
  LARKSUITE_CLI_APPLINK_BASE_URL

Values are trimmed and have trailing slashes stripped; empty or
whitespace-only values are ignored. Every existing call site already
routes through ResolveEndpoints, so OAuth device flow, app
registration, console scope links, consumer URLs, doctor checks, and
credential resolution all pick up the override automatically.

Public repo ships only the override mechanism — no internal hostnames.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously applyEndpointEnvOverrides accepted any non-empty value, even
though downstream code concatenates these bases to path segments (e.g.
ep.Open + "/open-apis/..."). A malformed value like "fsopen.bytedance.net"
(missing scheme) would produce invalid request URLs deep in the SDK.

- Require scheme in {http, https} and a non-empty host (path prefix
  allowed, since some deployments serve the API under a path).
- Warn on stderr when an override is set but invalid, then fall back to
  the brand default. Silent fallback would be dangerous: a typo'd inner
  host would route credentials to the public host without the user
  realizing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@wgzesg wgzesg force-pushed the feat/configurable-endpoint-base-urls branch from f978eb7 to cf42816 Compare May 25, 2026 08:19
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.

♻️ Duplicate comments (1)
internal/core/types.go (1)

116-127: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject hostless and query/fragment base URLs in validator.

Line 123 only checks u.Host, so values like http://:80 can pass; queries/fragments also pass today and can break downstream URL concatenation (internal/client/client.go:207-233).

🔧 Proposed fix
func isValidBaseURL(v string) bool {
	if v == "" {
		return false
	}
	u, err := url.Parse(v)
-	if err != nil || u.Host == "" {
+	if err != nil || u.Host == "" || u.Hostname() == "" {
+		return false
+	}
+	if u.RawQuery != "" || u.Fragment != "" {
		return false
	}
	return u.Scheme == "https" || u.Scheme == "http"
}
#!/bin/bash
set -euo pipefail

# Verify current validator conditions in code.
sed -n '116,130p' internal/core/types.go | cat -n

# Verify whether edge-case tests are present.
rg -n 'http://:80|\?x=|`#frag`' internal/core/types_test.go -S || true
🤖 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/core/types.go` around lines 116 - 127, The validator is too
permissive: isValidBaseURL currently only checks u.Host and allows hostless
forms like "http://:80" and URLs with queries/fragments; update isValidBaseURL
to also require a non-empty hostname (use u.Hostname() != "") and reject any URL
that contains a RawQuery or Fragment (u.RawQuery == "" and u.Fragment == ""),
while keeping the existing scheme check for "http"/"https" so downstream
concatenation won't break.
🤖 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.

Duplicate comments:
In `@internal/core/types.go`:
- Around line 116-127: The validator is too permissive: isValidBaseURL currently
only checks u.Host and allows hostless forms like "http://:80" and URLs with
queries/fragments; update isValidBaseURL to also require a non-empty hostname
(use u.Hostname() != "") and reject any URL that contains a RawQuery or Fragment
(u.RawQuery == "" and u.Fragment == ""), while keeping the existing scheme check
for "http"/"https" so downstream concatenation won't break.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ce27e75-4259-4913-986a-5f38ca656261

📥 Commits

Reviewing files that changed from the base of the PR and between f978eb7 and cf42816.

📒 Files selected for processing (2)
  • internal/core/types.go
  • internal/core/types_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants