Compare commits

...

1 Commits

Author SHA1 Message Date
zhaojunchang
0f0d77c653 docs: clarify lark-cli TAT flow boundaries 2026-06-09 16:16:49 +08:00
13 changed files with 55 additions and 1 deletions

View File

@@ -65,6 +65,14 @@ Both notices recommend the same fix command: `lark-cli update`. The skills notic
| `internal/vfs/` | Filesystem abstraction (use `vfs.*` instead of `os.*`) |
| `internal/validate/path.go` | Path safety validation |
## Auth / TAT operator notes
- The current TAT path is documented inline in `internal/credential/`, `cmd/config/init_probe.go`, `cmd/auth/status.go`, and `internal/identitydiag/diagnostics.go`. Historical materials that talk about `SEC_AUTH` or `cmd/sec/*` should be treated as drift, not as current-tree entry points.
- `config init` only performs a best-effort post-save probe. A typed auth error means the credentials were deterministically rejected; transport / timeout / parse failures are intentionally treated as ambiguous noise and do not prove the config is bad.
- `auth status --verify` and runtime bot commands share the same credential chain. A successful bot verify confirms the current token source can call `/open-apis/bot/v3/info`, but it does not prove every downstream bot scope or API path will work.
- Historical reports still matter: sandboxed runners may fail to read OS keychain state, and users can complete browser authorization while local state remains missing or stale. Treat those as environment / local-state risks to inspect, not proof that the remote authorization page never succeeded.
- The notes above are derived from current mainline code plus historical reports. They are intentionally not a live verification guarantee; real token usability still depends on valid app config, local state, and network reachability.
## Who Uses This CLI
This CLI's primary consumers include AI agents (Claude Code, Cursor, Gemini CLI). Your code is read by machines — error messages, output format, and flag design all directly affect agent success rates.

View File

@@ -40,6 +40,10 @@ func NewCmdAuthStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobr
return cmd
}
// authStatusRun reports the configured identities. When --verify is enabled,
// the bot branch does not mint a token independently — it reuses the shared
// credential resolution path, so `auth status --verify` exercises the same TAT
// source selection logic as normal bot API calls.
func authStatusRun(opts *StatusOptions) error {
f := opts.Factory

View File

@@ -28,6 +28,10 @@ const probeTimeout = 3 * time.Second
// so that valid configurations and transient/upstream noise never block the
// command.
//
// This is one of the repository's three main TAT entry points: config init
// validates freshly-saved app credentials here, while runtime bot calls and
// `auth status --verify` go through credential.ResolveToken.
//
// The function performs up to two HTTP calls in series, bounded by
// probeTimeout:
//

View File

@@ -322,7 +322,10 @@ func sanitizeOutputDir(dir string) (string, error) {
return safe, nil
}
// resolveTenantToken fetches the app's tenant access token.
// resolveTenantToken fetches the app's tenant access token for event consume.
// This is a concrete runtime consumer of the shared bot-token path: event
// consume does not own a separate exchange flow, it asks the credential layer
// for the same TAT that other bot-mode commands use.
func resolveTenantToken(ctx context.Context, f *cmdutil.Factory, appID string) (string, error) {
if ctx == nil {
ctx = context.Background()

View File

@@ -94,6 +94,9 @@ func (p *Provider) ResolveAccount(ctx context.Context) (*credential.Account, err
return acct, nil
}
// ResolveToken returns a token directly from environment variables. For bot
// identity this is a straight TAT override that bypasses the built-in
// app_id/app_secret mint path entirely.
func (p *Provider) ResolveToken(ctx context.Context, req credential.TokenSpec) (*credential.Token, error) {
var envKey string
switch req.Type {

View File

@@ -103,6 +103,8 @@ func (p *Provider) ResolveAccount(ctx context.Context) (*credential.Account, err
// ResolveToken returns a sentinel token whose value encodes the token type.
// The transport interceptor reads this sentinel to determine the identity
// (user vs bot), strips it, and the sidecar injects the real token.
// In other words, sidecar mode proxies the normal UAT/TAT contract; it does
// not introduce a new open-platform token-exchange protocol inside this repo.
// Returns nil, nil when sidecar mode is not active.
func (p *Provider) ResolveToken(ctx context.Context, req credential.TokenSpec) (*credential.Token, error) {
if os.Getenv(envvars.CliAuthProxy) == "" {

View File

@@ -64,6 +64,9 @@ type Interceptor struct {
// Supports two auth patterns:
// - Standard OpenAPI: Authorization: Bearer <sentinel>
// - MCP protocol: X-Lark-MCP-UAT/TAT: <sentinel>
//
// For bot traffic the sentinel stands in for TAT only long enough for the
// proxy hop; the real tenant access token is injected by the sidecar.
func (i *Interceptor) PreRoundTrip(req *http.Request) func(resp *http.Response, err error) {
identity, authHeader := detectSentinel(req)
if identity == "" {

View File

@@ -301,6 +301,16 @@ func (p *CredentialProvider) doResolveIdentityHint(ctx context.Context) (*Identi
}
// ResolveToken resolves an access token.
//
// For bot identity this is the runtime TAT entry point used across the CLI:
// 1. an active credential source (for example env or sidecar) gets first
// chance to return a token;
// 2. extension providers are tried next;
// 3. the built-in default provider falls back to FetchTAT(app_id, app_secret)
// and caches that tenant access token with sync.Once for the process.
//
// The env provider may supply a real TAT directly; the sidecar provider returns
// a sentinel token that the transport layer rewrites into a proxied request.
func (p *CredentialProvider) ResolveToken(ctx context.Context, req TokenSpec) (*TokenResult, error) {
source, err := p.selectedCredentialSource(ctx)
if err != nil {

View File

@@ -147,6 +147,8 @@ func (p *DefaultTokenProvider) resolveUAT(ctx context.Context) (*TokenResult, er
}
// resolveTAT resolves a tenant access token. Result is cached after first call.
// This is the built-in runtime path behind bot token resolution when no
// external credential source overrides it.
// NOTE: Uses sync.Once — only the context from the first call is used.
func (p *DefaultTokenProvider) resolveTAT(ctx context.Context) (*TokenResult, error) {
p.tatOnce.Do(func() {

View File

@@ -18,6 +18,11 @@ import (
// that already hold plaintext credentials (e.g. the post-`config init` probe)
// can validate them without a second keychain round-trip.
//
// This is the built-in TAT mint path for the current repository:
// app_id + app_secret -> /open-apis/auth/v3/tenant_access_token/internal.
// It is reused by both the post-config probe and the default runtime bot-token
// resolver. The tree does not contain a separate app_ticket-based TAT exchange.
//
// A non-zero TAT response code means the server inspected the payload and
// rejected the credentials; FetchTAT returns the canonical typed error from
// classifyTATResponseCode — the SAME classification doResolveTAT (and thus

View File

@@ -97,6 +97,10 @@ type AccountProvider interface {
}
// TokenType distinguishes UAT from TAT.
// In this repository, TAT always means "tenant access token" for bot identity:
// either minted from app_id + app_secret by the built-in provider, supplied
// directly by an extension provider, or proxied through authsidecar. There is
// no app_ticket -> tenant_access_token exchange implementation in this tree.
// Uses string constants matching extension/credential.TokenType for zero-cost conversion.
type TokenType string

View File

@@ -217,6 +217,10 @@ func diagnoseUser(ctx context.Context, f *cmdutil.Factory, cfg *core.CliConfig,
return id
}
// resolveBotToken is the bot-verification consumer of the shared credential
// chain. It intentionally reuses ResolveToken(core.AsBot, appID) so auth
// diagnostics observe the same env-provider, sidecar, strict-mode, and
// built-in FetchTAT behavior as normal runtime bot commands.
func resolveBotToken(ctx context.Context, f *cmdutil.Factory, cfg *core.CliConfig) (string, error) {
if f == nil || f.Credential == nil {
return "", &credential.TokenUnavailableError{Type: credential.TokenTypeTAT}

View File

@@ -8,6 +8,8 @@
// compiled in under that tag; deploying the plain build into an environment
// that expects sidecar isolation would silently fall back to direct env
// credential use — exactly the failure mode the feature is meant to prevent.
// Without the authsidecar build tag, the binary cannot safely proxy either UAT
// or TAT through the sidecar contract at all.
//
// When LARKSUITE_CLI_AUTH_PROXY is set, we refuse to run rather than ignore
// the variable. The operator either rebuilt without realizing (wrong