policyfile

package
v0.2.49 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: GPL-2.0, GPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package policyfile reads and writes kiro-cli's native Cedar permission policy files (permissions.yaml) for the user and workspace scopes.

vibekit is the sole programmatic writer of these files on the acp bridge: KAS only READS them there (its acp consent dialog offers allow_once / reject_once, never a persisted "always", so KAS's own addRuleToFile never runs on the acp path). KAS hot-reloads the file on change via a chokidar watcher, emitting _kiro/policy/changed — so a write here takes effect on every live session with no bridge restart (verified live).

On-disk shape (verified against the KAS 2.12 acp-server bundle + live):

rules:
  - capability: fs_write        # required; one of the known capabilities
    effect: ask                 # required; allow | deny | ask
    match: ["src/**"]           # optional glob list
    exclude: ["**/secret.txt"]  # optional glob list

Load tolerates BOTH block YAML (KAS's / a hand-editing user's format) and JSON (JSON is valid YAML 1.2). We MARSHAL block YAML so files stay human readable and consistent with KAS's own writer.

Path facts (verified live — KAS resolves the base from $HOME via os.homedir(), NOT KIRO_HOME):

user:      <home>/.kiro/settings/permissions.yaml
workspace: <home>/.kiro/workspace-roots/<hash>/permissions.yaml
           hash = hex(sha256(abs(workDir)))[:16]   (KAS computeWorkspaceHash)

POLICY_FILENAMES is ["permissions.yaml", "permissions.json"] and KAS's loadPolicyFromDir returns the FIRST that EXISTS, so we always target the .yaml name to stay authoritative.

Index

Constants

View Source
const (
	ScopeUser      = "user"
	ScopeWorkspace = "workspace"
)

Scope names vibekit can write. Kiro/administration are read-only baselines; agent comes from the agent profile; session is runtime.

View Source
const (
	EffectAllow = "allow"
	EffectDeny  = "deny"
	EffectAsk   = "ask"
)

Effect values.

Variables

View Source
var (
	ErrInvalidScope      = errors.New("scope must be user or workspace")
	ErrInvalidCapability = errors.New("unknown capability")
	ErrInvalidEffect     = errors.New("effect must be allow, deny, or ask")
	ErrTooManyRules      = errors.New("policy file has too many rules")
	ErrPatternTooLong    = errors.New("match/exclude pattern too long")
	ErrPatternInvalid    = errors.New("match/exclude pattern contains invalid characters")
)

Errors surfaced to the HTTP edge.

Functions

func Capabilities

func Capabilities() []string

Capabilities returns the writable capability set, sorted, for the UI picker. The meta caps come first (broadest), then the concrete ones.

func PathFor

func PathFor(scope, home, workDir string) (string, error)

PathFor returns the permissions.yaml path for the given scope. home is the base KAS resolves from ($HOME); workDir is the bridge's cwd (only needed for the workspace scope).

func Save

func Save(ctx context.Context, path string, f *File) error

Save writes the file atomically (temp → fsync → rename → dir-fsync) via cplieger/atomicfile, mode 0600 (policy is sensitive), creating parent dirs at 0700. A crash mid-write can never leave a truncated policy file (which would silently drop every rule at the next KAS reload).

func Signature

func Signature(r *Rule) string

Signature is the dedup/equality key for a rule: capability + effect + sorted match + sorted exclude. Mirrors KAS ruleSignature so vibekit's notion of "same rule" matches the engine's.

func ValidCapability

func ValidCapability(capability string) bool

ValidCapability reports whether capability is a known KAS capability.

func ValidEffect

func ValidEffect(effect string) bool

ValidEffect reports whether effect is allow/deny/ask.

func ValidScope

func ValidScope(scope string) bool

ValidScope reports whether scope is writable by vibekit.

func WorkspaceHash

func WorkspaceHash(workDir string) string

WorkspaceHash mirrors KAS computeWorkspaceHash on Linux: the first 16 hex chars of sha256 over the workspace root. The root is canonicalized here — absolute, lexically cleaned, no "." / ".." segments, no trailing slash — to match the path.resolve output KAS hashes on its side. Canonicalizing at the hash (rather than trusting workDir verbatim) keeps vibekit's workspace-roots/<hash> directory in lockstep with KAS's for any non-canonical KIRO_WORK_DIR — a trailing slash, a "/a/../b" form, or a relative value. A divergent hash would silently write workspace-scope rules to a directory KAS never reads, so the rules would persist yet never be enforced.

Types

type File

type File struct {
	Rules []Rule `yaml:"rules" json:"rules"`
}

File is the whole permissions.yaml document.

func Load

func Load(path string) (*File, error)

Load reads and parses a permissions file. A missing file yields an empty File and nil error (the common "no rules yet" case). Parse failures and oversize files are errors so the caller never silently clobbers a hand-authored file it couldn't understand.

func (*File) Has

func (f *File) Has(r *Rule) bool

Has reports whether an identical rule (by Signature) already exists.

func (*File) Remove

func (f *File) Remove(r *Rule) bool

Remove deletes the first rule matching r by Signature. Returns true if a rule was removed.

func (*File) ReplaceEffect added in v0.2.8

func (f *File) ReplaceEffect(old *Rule, effect string) bool

ReplaceEffect changes the effect of the rule matching old (by Signature) IN PLACE, preserving its position in the file — one atomic mutation, so an in-place edit can never half-apply the way a client-side remove+add could. When the resulting rule would duplicate an existing one, the old rule is removed instead (the duplicate already expresses the target state). Returns true if the file changed: false means the old rule is absent or the effect is already effect.

func (*File) Upsert

func (f *File) Upsert(r *Rule) (bool, error)

Upsert appends r if no identical rule exists. Returns true if the file changed. Discrete-rule semantics (no auto-merge into match arrays) so each editor rule maps to exactly one removable YAML rule — predictable for a security-sensitive file.

type Rule

type Rule struct {
	Capability string   `yaml:"capability" json:"capability"`
	Effect     string   `yaml:"effect" json:"effect"`
	Match      []string `yaml:"match,omitempty" json:"match,omitempty"`
	Exclude    []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
}

Rule is one policy rule. yaml tags drive the on-disk block YAML; json tags keep it decodable from the REST layer. Field order (capability, effect, match, exclude) is the canonical KAS order.

func SanitizeRule

func SanitizeRule(r *Rule) (Rule, error)

SanitizeRule validates + normalizes a rule for writing. It trims and de-dups match/exclude, enforces the length/count caps, and validates the capability + effect. It does NOT default the effect — the caller applies the conservative "default to ask" policy so the choice is explicit at the edge.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL