mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 18:13:01 +08:00
Add a single public extension contract under extension/platform: integrators
implement the Plugin interface and register Observers, Wrappers, Lifecycle
handlers, and pruning Rules through the Registrar in one Install call.
Command pruning:
- Rule (Allow / Deny / MaxRisk / Identities) with doublestar globs
- 4-axis AND evaluation, parent-group aggregation, unknown-risk allow
- Sources: Plugin.Restrict (single-rule) and ~/.lark-cli/policy.yml
- Plugin path is fail-closed (envelope on rule error / multiple Restrict);
yaml path is fail-open (warning, CLI continues)
- strict-mode stubs now also write the denial annotation so the hook
layer's denial guard physically isolates Wrap chains on them
- HOME path never leaked through policy_source label
Hook framework:
- Observer (panic-safe, Before/After), Wrapper (middleware, may short-circuit
via AbortError), Lifecycle (Startup + Shutdown only)
- Recover guards every plugin entry point: Capabilities(), Install(),
Wrapper factory composition AND inner Handler, Lifecycle handlers
- namespacedWrap copies AbortError so a plugin's package-level sentinel
is never mutated across concurrent invocations
- Selector unknown-risk uniform: ByExactRisk / ByWrite / ByReadOnly never
match unannotated commands; safety-side hooks opt in via
ByWrite().Or(ByUnknownRisk())
Bootstrap orchestration (cmd/build.go + cmd/policy.go):
- InstallAll uses a staging Registrar + atomic commit
- FailClosed plugin install / Plugin.Restrict conflict / Startup handler
failure each install a structured envelope guard at every dispatch path
- walkGuard neutralises every cobra bypass we know of (PersistentPreRunE
first-wins, ValidateArgs, ParseFlags, legacyArgs, __complete /
__completeNoDesc, non-runnable groups, required-arg subcommands)
- cmd/root.go::Execute calls hook.Emit(Shutdown, runErr) after
rootCmd.Execute; isCompletionCommand skips both __complete and
__completeNoDesc so Tab completion never triggers Shutdown handlers
Capabilities consistency:
- Restricts=true must declare FailurePolicy=FailClosed
- RequiredCLIVersion (semver constraint) is validated against build.Version;
a malformed constraint is treated as untrusted-config and aborts
unconditionally, regardless of FailurePolicy (DEV builds included)
JSON envelope contract:
- error.type closed enum: pruning / strict_mode / hook / plugin_install /
plugin_conflict / plugin_lifecycle
- reason_code closed enums per type, all referenced by structured tests
Bootstrap surfaces (new user commands):
- lark-cli config policy show -- JSON view of the active Rule + source
- lark-cli config policy validate -- parse + schema + glob check, no apply
Coverage:
- extension/platform: every public type has a unit test
- internal/{pruning,hook,platformhost,policydecision,cmdmeta}: full coverage
of denial guard isolation, AbortError sentinel safety, observer panic
safety, lifecycle error/panic typing, staging atomic rollback
- cmd/plugin_integration_test.go: end-to-end through buildInternal with
synthetic and real command trees
- cmd/install_guard_test.go: walkGuard covers auth / config / __complete /
__completeNoDesc / non-runnable parents
41 lines
1.3 KiB
Go
41 lines
1.3 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package platform
|
|
|
|
import "fmt"
|
|
|
|
// CommandDeniedError is the structured error returned by a denyStub. Every
|
|
// pruned-command execution path -- direct invocation, alias expansion,
|
|
// internal call -- returns this exact type. It is wire-compatible with the
|
|
// output.ExitError envelope via the Layer (== error.type) field and the
|
|
// detail map produced by ExitError().
|
|
//
|
|
// Layer values:
|
|
//
|
|
// - "strict_mode" -- credential strict-mode rejected the command
|
|
// - "pruning" -- user-layer Rule rejected the command
|
|
//
|
|
// PolicySource is a free-form identifier such as "plugin:secaudit",
|
|
// "yaml:mywork", or "strict-mode". Reason fields:
|
|
//
|
|
// - ReasonCode -- closed enum, see tech-doc 5.3 (e.g. write_not_allowed,
|
|
// all_children_denied, identity_not_supported)
|
|
// - Reason -- human-readable text
|
|
type CommandDeniedError struct {
|
|
Path string
|
|
Layer string
|
|
PolicySource string
|
|
RuleName string
|
|
ReasonCode string
|
|
Reason string
|
|
}
|
|
|
|
// Error implements the standard error interface.
|
|
func (e *CommandDeniedError) Error() string {
|
|
if e.Reason != "" {
|
|
return fmt.Sprintf("command %q denied: %s", e.Path, e.Reason)
|
|
}
|
|
return fmt.Sprintf("command %q denied (%s/%s)", e.Path, e.Layer, e.ReasonCode)
|
|
}
|