chore: import upstream snapshot with attribution
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmdpolicy_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/extension/platform"
|
||||
"github.com/larksuite/cli/internal/cmdpolicy"
|
||||
)
|
||||
|
||||
// The envelope's policy_source must never leak the absolute home path.
|
||||
// "yaml:/Users/alice/.lark-cli/policy.yml" would expose Alice's username
|
||||
// to any agent or log consumer; the contract is to emit just "yaml" and
|
||||
// rely on rule_name (from the yaml's "name:" field) for disambiguation.
|
||||
func TestEnvelope_yamlPolicySourceDoesNotLeakHomePath(t *testing.T) {
|
||||
root := &cobra.Command{Use: "lark-cli"}
|
||||
docs := &cobra.Command{Use: "docs"}
|
||||
root.AddCommand(docs)
|
||||
leaf := &cobra.Command{Use: "+write", RunE: func(*cobra.Command, []string) error { return nil }}
|
||||
docs.AddCommand(leaf)
|
||||
|
||||
e := cmdpolicy.New(&platform.Rule{
|
||||
Name: "my-readonly-rule",
|
||||
Allow: []string{"contact/**"}, // docs/* falls outside, denied
|
||||
})
|
||||
denied := cmdpolicy.BuildDeniedByPath(root, e.EvaluateAll(root),
|
||||
cmdpolicy.ResolveSource{
|
||||
Kind: cmdpolicy.SourceYAML,
|
||||
Name: "/Users/alice/.lark-cli/policy.yml", // simulate an absolute path
|
||||
}, "my-readonly-rule")
|
||||
|
||||
cmdpolicy.Apply(root, denied)
|
||||
err := leaf.RunE(leaf, nil)
|
||||
|
||||
var ve *errs.ValidationError
|
||||
if !errors.As(err, &ve) {
|
||||
t.Fatalf("expected denial *errs.ValidationError, got %T %v", err, err)
|
||||
}
|
||||
// The policy source is folded into the Hint as "yaml" -- the bare
|
||||
// kind, never the absolute path.
|
||||
if !strings.Contains(ve.Hint, "source yaml") {
|
||||
t.Errorf("hint must carry policy_source %q (no path leak), got %q", "yaml", ve.Hint)
|
||||
}
|
||||
// rule_name carries the disambiguating identifier.
|
||||
if !strings.Contains(ve.Hint, "my-readonly-rule") {
|
||||
t.Errorf("hint must carry rule_name my-readonly-rule, got %q", ve.Hint)
|
||||
}
|
||||
// Direct privacy probe: the absolute home path must not appear
|
||||
// anywhere in the user-facing message OR hint text.
|
||||
if strings.Contains(ve.Message, "/Users/alice") {
|
||||
t.Errorf("error message must not leak '/Users/alice', got %q", ve.Message)
|
||||
}
|
||||
if strings.Contains(ve.Hint, "/Users/alice") {
|
||||
t.Errorf("error hint must not leak '/Users/alice', got %q", ve.Hint)
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin name IS allowed in policy_source because plugins are in-binary
|
||||
// and their names are part of the contract (an integrator debugging a
|
||||
// denial wants to know which plugin fired). This test pins that intent
|
||||
// so a future change does not silently strip the plugin name too.
|
||||
func TestEnvelope_pluginPolicySourceCarriesName(t *testing.T) {
|
||||
root := &cobra.Command{Use: "lark-cli"}
|
||||
leaf := &cobra.Command{Use: "+block", RunE: func(*cobra.Command, []string) error { return nil }}
|
||||
root.AddCommand(leaf)
|
||||
|
||||
e := cmdpolicy.New(&platform.Rule{
|
||||
Name: "secaudit-policy",
|
||||
Deny: []string{"+block"},
|
||||
})
|
||||
denied := cmdpolicy.BuildDeniedByPath(root, e.EvaluateAll(root),
|
||||
cmdpolicy.ResolveSource{Kind: cmdpolicy.SourcePlugin, Name: "secaudit"},
|
||||
"secaudit-policy")
|
||||
cmdpolicy.Apply(root, denied)
|
||||
|
||||
err := leaf.RunE(leaf, nil)
|
||||
var ve *errs.ValidationError
|
||||
if !errors.As(err, &ve) {
|
||||
t.Fatalf("expected *errs.ValidationError, got %T", err)
|
||||
}
|
||||
// The plugin name IS surfaced (in-binary, part of the contract): it
|
||||
// must appear in the Hint so an integrator debugging a denial knows
|
||||
// which plugin fired.
|
||||
if !strings.Contains(ve.Hint, "plugin:secaudit") {
|
||||
t.Errorf("hint must carry policy_source plugin:secaudit, got %q", ve.Hint)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user