Skip to content

feat(cmdutil): add X-Cli-Build header for CLI build classification#596

Merged
sang-neo03 merged 2 commits intomainfrom
feat/cli-build-telemetry
Apr 22, 2026
Merged

feat(cmdutil): add X-Cli-Build header for CLI build classification#596
sang-neo03 merged 2 commits intomainfrom
feat/cli-build-telemetry

Conversation

@sang-neo03
Copy link
Copy Markdown
Collaborator

@sang-neo03 sang-neo03 commented Apr 22, 2026

Summary

Adds X-Cli-Build (official / extended / unknown) so the gateway can tell the official CLI apart from ISV-repackaged builds. Detection runs at request time (not from
init()) to avoid misclassifying extended builds whose providers register later.

Changes

  • secheader.go: add HeaderBuild constant, DetectBuildKind() (cached via sync.Once), computeBuildKind() (main-module-path + non-builtin provider check), and
    isBuiltinProvider() using reflect.Type.PkgPath so Name() can't spoof classification.
  • BaseSecurityHeaders() now emits X-Cli-Build alongside existing headers.
  • transport.go: add BuildHeaderTransport that force-writes X-Cli-Build on every request, protecting the SDK chain where SecurityHeaderTransport isn't installed.
  • factory_default.go: wire BuildHeaderTransport into buildSDKTransport().
  • Tests cover builtin/non-builtin classification, stdlib-type rejection, sync.Once caching, sidecar providers (under authsidecar tag), and transport header injection.

Test Plan

  • go test ./internal/cmdutil/... -count=1 passes
  • Local probe reproducing the SDK transport chain: header reaches both loopback and httpbin.org/anything over the real network, for both official and extended values
  • strings on the built binary shows X-Cli-Build / DetectBuildKind / computeBuildKind symbols are compiled in

Related Issues

  • None

Summary by CodeRabbit

  • New Features
    • Automatic build classification (official/extended/unknown) is detected and sent with client requests; transport now enforces this header so it cannot be tampered with.
  • Tests
    • Added comprehensive tests for build detection, header injection, tamper-resistance, and sidecar-provider classification.

  Adds X-Cli-Build (official / extended / unknown) so the gateway can distinguish official CLI from ISV-repackaged builds.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

Adds build-kind detection and a new transport that injects an X-Cli-Build header (official/extended/unknown) into SDK HTTP requests. Build kind is computed once per process using module build info and provider classification; the transport enforces the header to prevent tampering.

Changes

Cohort / File(s) Summary
Build detection & headers
internal/cmdutil/secheader.go
Adds HeaderBuild (X-Cli-Build), BuildKind* constants, cached detection via DetectBuildKind()/computeBuildKind(), isBuiltinProvider() using reflection, and updates BaseSecurityHeaders() to include the build header.
Transport layer & integration
internal/cmdutil/transport.go, internal/cmdutil/factory_default.go
Introduces BuildHeaderTransport RoundTripper that force-sets X-Cli-Build on outgoing requests and inserts it into the SDK transport chain (placed around existing UA/Retry/Auth transports).
Tests — header classification
internal/cmdutil/secheader_test.go, internal/cmdutil/secheader_sidecar_test.go
New comprehensive tests for provider classification, computeBuildKind branches, caching behavior, and sidecar-mode provider classification (build-tag guarded).
Tests — transport behavior
internal/cmdutil/transport_test.go
Updates transport-chain assertions to include BuildHeaderTransport; adds tests confirming header injection, override protection against tampering, and behavior when Base is nil.

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant BuildHeader as BuildHeaderTransport
    participant SecPolicy as SecurityPolicyTransport
    participant Ext as ExtensionMiddleware
    participant Server

    CLI->>BuildHeader: HTTP Request
    BuildHeader->>BuildHeader: set Header "X-Cli-Build" = DetectBuildKind()
    BuildHeader->>SecPolicy: forward request
    SecPolicy->>Ext: apply security / extension middleware
    Ext->>Server: send network request
    Server-->>CLI: response (via reverse chain)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

size/XL

Suggested reviewers

  • liangshuo-1

Poem

🐰 I hop a header on each net-bound trail,
X-Cli-Build set true, no spoofing can prevail.
Official, extended, or unknown I say,
Cached with care, computed once per day. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 accurately and concisely summarizes the main change: adding an X-Cli-Build header for CLI build classification, which aligns with all file modifications.
Description check ✅ Passed The description includes all required template sections (Summary, Changes, Test Plan, Related Issues) with comprehensive details about implementation, testing approach, and verification steps.
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/cli-build-telemetry

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 Apr 22, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 90.76923% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.88%. Comparing base (3e5dc32) to head (486d991).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
internal/cmdutil/secheader.go 89.47% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #596      +/-   ##
==========================================
- Coverage   61.69%   59.88%   -1.82%     
==========================================
  Files         402      404       +2     
  Lines       35073    42592    +7519     
==========================================
+ Hits        21639    25505    +3866     
- Misses      11429    15080    +3651     
- Partials     2005     2007       +2     

☔ 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 Apr 22, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@486d9910547ad7767adf49d833349a1d9305c6b7

🧩 Skill update

npx skills add larksuite/cli#feat/cli-build-telemetry -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: 1

Caution

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

⚠️ Outside diff range comments (1)
internal/cmdutil/factory_default.go (1)

115-123: ⚠️ Potential issue | 🟠 Major

Avoid freezing DetectBuildKind() before the SDK request path runs.

lark.WithHeaders(BaseSecurityHeaders()) now calls DetectBuildKind() while constructing the SDK client, so sync.Once can cache official before a later provider registration. Then the new BuildHeaderTransport cannot correct the value at request time because it reads the already-cached result.

Keep X-Cli-Build out of the static SDK headers and let BuildHeaderTransport be the SDK path that calls DetectBuildKind() during RoundTrip.

Proposed fix
diff --git a/internal/cmdutil/secheader.go b/internal/cmdutil/secheader.go
@@
 func BaseSecurityHeaders() http.Header {
+	h := baseSecurityHeaders()
+	h.Set(HeaderBuild, DetectBuildKind())
+	return h
+}
+
+func baseSecurityHeaders() http.Header {
 	h := make(http.Header)
 	h.Set(HeaderSource, SourceValue)
 	h.Set(HeaderVersion, build.Version)
-	h.Set(HeaderBuild, DetectBuildKind())
 	h.Set(HeaderUserAgent, UserAgentValue())
 	return h
 }
diff --git a/internal/cmdutil/factory_default.go b/internal/cmdutil/factory_default.go
@@
 		opts := []lark.ClientOptionFunc{
 			lark.WithEnableTokenCache(false),
 			lark.WithLogLevel(larkcore.LogLevelError),
-			lark.WithHeaders(BaseSecurityHeaders()),
+			lark.WithHeaders(baseSecurityHeaders()),
 		}

Also applies to: 131-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmdutil/factory_default.go` around lines 115 - 123, The SDK client
is being constructed with lark.WithHeaders(BaseSecurityHeaders()) which triggers
DetectBuildKind() early and caches the build kind; remove the X-Cli-Build header
from the static headers returned by BaseSecurityHeaders() when creating the opts
slice in factory_default.go and instead rely on BuildHeaderTransport (used by
buildSDKTransport()/Transport) to call DetectBuildKind() and inject X-Cli-Build
at request time in its RoundTrip implementation; update any code paths that
append headers to opts (the lark.WithHeaders call) to pass only static security
headers and ensure BuildHeaderTransport remains responsible for setting the
dynamic X-Cli-Build header.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cmdutil/secheader_test.go`:
- Line 25: The test intends to verify classification ignores Name(), but the
test-local provider's Name() currently returns "local" so it doesn't spoof;
change the test doubles' Name() implementations (e.g.,
cmdutilLocalProvider.Name()) to return an obviously external/non-builtin value
such as "external-local" or "spoofed-name" so the test truly exercises
Name-based spoofing; update the other test provider Name() occurrences
referenced in the same test (the other block around lines 54-64) to the same
external value to ensure the test fails on any regression that relies on Name().

---

Outside diff comments:
In `@internal/cmdutil/factory_default.go`:
- Around line 115-123: The SDK client is being constructed with
lark.WithHeaders(BaseSecurityHeaders()) which triggers DetectBuildKind() early
and caches the build kind; remove the X-Cli-Build header from the static headers
returned by BaseSecurityHeaders() when creating the opts slice in
factory_default.go and instead rely on BuildHeaderTransport (used by
buildSDKTransport()/Transport) to call DetectBuildKind() and inject X-Cli-Build
at request time in its RoundTrip implementation; update any code paths that
append headers to opts (the lark.WithHeaders call) to pass only static security
headers and ensure BuildHeaderTransport remains responsible for setting the
dynamic X-Cli-Build header.
🪄 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: 4c6ef540-9d69-49f6-a2ce-e44eef024025

📥 Commits

Reviewing files that changed from the base of the PR and between f369929 and 971e320.

📒 Files selected for processing (6)
  • internal/cmdutil/factory_default.go
  • internal/cmdutil/secheader.go
  • internal/cmdutil/secheader_sidecar_test.go
  • internal/cmdutil/secheader_test.go
  • internal/cmdutil/transport.go
  • internal/cmdutil/transport_test.go

Comment thread internal/cmdutil/secheader_test.go Outdated
Extract classifyBuild as a pure helper so every branch (unknown / extended
main-path / extended credential / extended transport / extended fileio /
official) is reachable without mutating process-wide provider registries.

Also cover: isBuiltinProvider non-pointer values, BuildHeaderTransport
nil-Base fallback path, and fix the Name-spoof test so the test double
returns a value that actually mimics an ISV provider.

Coverage on PR-changed functions:
- classifyBuild: 100% (new)
- computeBuildKind: 61.5% -> 93.3%
- BuildHeaderTransport.RoundTrip: 80% -> 100%
@sang-neo03 sang-neo03 force-pushed the feat/cli-build-telemetry branch from b4d6b3a to 486d991 Compare April 22, 2026 07:03
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/cmdutil/secheader_test.go (1)

230-236: Optional: strengthen the sync.Once caching assertion.

TestDetectBuildKind_StableAcrossCalls only checks that two calls return equal values, which is also true for a non-cached pure function. If you want this test to actually pin the sync.Once contract (and not just value stability), consider asserting the cached variable is populated after the first call, or at minimum documenting that stability — not caching — is what's being verified here. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmdutil/secheader_test.go` around lines 230 - 236, The test only
verifies value stability but not that DetectBuildKind actually caches via
sync.Once; update TestDetectBuildKind_StableAcrossCalls to assert the caching
behavior by checking the package-level cache is populated after the first call
(for example assert the cached buildKind variable is non-empty or set) or, if
that variable is unexported/unavailable, change the test to explicitly document
that it only verifies stability (or add a test helper that exposes the cached
state) — make changes referencing DetectBuildKind and the sync.Once-backed cache
so the test proves the sync.Once contract rather than just equal return values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/cmdutil/secheader_test.go`:
- Around line 230-236: The test only verifies value stability but not that
DetectBuildKind actually caches via sync.Once; update
TestDetectBuildKind_StableAcrossCalls to assert the caching behavior by checking
the package-level cache is populated after the first call (for example assert
the cached buildKind variable is non-empty or set) or, if that variable is
unexported/unavailable, change the test to explicitly document that it only
verifies stability (or add a test helper that exposes the cached state) — make
changes referencing DetectBuildKind and the sync.Once-backed cache so the test
proves the sync.Once contract rather than just equal return values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 669549f0-1578-4132-9bf5-5e21d95e45c3

📥 Commits

Reviewing files that changed from the base of the PR and between 971e320 and 486d991.

📒 Files selected for processing (3)
  • internal/cmdutil/secheader.go
  • internal/cmdutil/secheader_test.go
  • internal/cmdutil/transport_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/cmdutil/transport_test.go
  • internal/cmdutil/secheader.go

@sang-neo03 sang-neo03 merged commit 18e227f into main Apr 22, 2026
20 checks passed
@sang-neo03 sang-neo03 deleted the feat/cli-build-telemetry branch April 22, 2026 08:30
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