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
- Variables
- func Capabilities() []string
- func PathFor(scope, home, workDir string) (string, error)
- func Save(ctx context.Context, path string, f *File) error
- func Signature(r *Rule) string
- func ValidCapability(capability string) bool
- func ValidEffect(effect string) bool
- func ValidScope(scope string) bool
- func WorkspaceHash(workDir string) string
- type File
- type Rule
Constants ¶
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.
const ( EffectAllow = "allow" EffectDeny = "deny" EffectAsk = "ask" )
Effect values.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
ValidCapability reports whether capability is a known KAS capability.
func ValidEffect ¶
ValidEffect reports whether effect is allow/deny/ask.
func ValidScope ¶
ValidScope reports whether scope is writable by vibekit.
func WorkspaceHash ¶
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 ¶
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) Remove ¶
Remove deletes the first rule matching r by Signature. Returns true if a rule was removed.
func (*File) ReplaceEffect ¶ added in v0.2.8
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.
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 ¶
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.