perms

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package perms reads Claude Code permission rules, reports the ones that cannot do what their author intended, and answers whether a given command is covered by them.

It is public rather than internal because two products depend on it: pitwall audits a ruleset with it, and steward enforces one with it. They must agree — an audit that says a rule is dead while the enforcer still honours it is worse than either tool alone — so there is one matcher and both import it.

The matching rules implemented here follow Claude Code's documented behaviour. They are deliberately conservative: where the documentation is silent, this package declines to make a claim rather than guessing, because a linter that invents findings is worse than no linter.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(p Plan, backupDir string) (backup string, err error)

Apply writes the plan to disk after copying the original into backupDir. It rebuilds only the permission lists and leaves every other key byte-identical and in its original order, so the diff a user reviews is the change itself and nothing else.

func CategoryRank

func CategoryRank(c string) int

CategoryRank orders findings for display.

func Covers

func Covers(a, b string) bool

Covers reports whether pattern a approves everything pattern b approves, so b adds nothing. It is answered only for cases that can be decided by inspection: an exact duplicate, or a literal b that a already matches. Anything else returns false rather than a guess.

func HasSeparator

func HasSeparator(s string) bool

HasSeparator reports whether s contains a shell command separator.

func ManagedPath

func ManagedPath() string

ManagedPath is where an administrator's settings live on this platform.

func MatchBash

func MatchBash(pattern, cmd string) bool

MatchBash reports whether a pattern approves a whole command line. Every subcommand must match independently, so Bash(safe-cmd *) does not approve "safe-cmd && other-cmd".

func MatchBashPattern

func MatchBashPattern(pattern, sub string) bool

MatchBashPattern reports whether a single Bash pattern matches one subcommand. It does not split on separators; use MatchBash for a full command line.

func ParseRule

func ParseRule(raw string) (tool, arg string, bare bool)

ParseRule splits "Tool(arg)" into its parts. An entry with no parentheses is a bare tool name, which in a deny rule removes the tool from Claude's context entirely.

func SplitCommand

func SplitCommand(cmd string) (parts []string, ok bool)

SplitCommand breaks a command line into the subcommands Claude Code matches separately. Quoted regions are left alone, so "echo 'a && b'" stays one subcommand — splitting inside quotes would invent separators that the shell never sees and make a working rule look unused. A trailing separator with nothing after it makes the whole command unparseable, which Claude Code treats as "no allow rule applies"; ok reports that case.

Types

type Finding

type Finding struct {
	Rule     Rule
	Category string
	Why      string
	Fix      string // what to write instead; empty means the rule should go
	Redacted bool   // the rule text carries a secret and must not be printed
}

Finding is one rule that does not do what its author intended, with the documented reason it fails and what to do instead.

func Lint

func Lint(rules []Rule) []Finding

Lint reports every finding across the rules given. Rules are examined together so that shadowing and duplication can be seen.

type Options

type Options struct {
	// DropOneOffs also removes literal allow rules. They work; they are just
	// clutter that only ever matches one command again. Removing one means
	// Claude Code asks about that command the next time, which is a change in
	// behaviour, so it is off by default.
	DropOneOffs bool
}

Options controls how aggressive a fix plan is.

type Plan

type Plan struct {
	Source  Source
	Remove  []Finding
	Rewrite []Rewrite
	Report  []Finding // findings this plan deliberately leaves alone
}

Plan is the set of edits for a single settings file.

func PlanFixes

func PlanFixes(findings []Finding, opts Options) []Plan

PlanFixes turns findings into per-file edits. Only rules that cannot do anything at all are removed, and a rewrite is applied only when it narrows or preserves what is permitted.

func (Plan) Empty

func (p Plan) Empty() bool

Empty reports whether the plan would change nothing.

type Prefix

type Prefix struct {
	Pattern string `json:"pattern"`
	Covers  int    `json:"covers"`
}

Prefix is a candidate rule that would replace several one-off rules.

func Prefixes

func Prefixes(findings []Finding, minCount int) []Prefix

Prefixes groups one-off rules by the command prefix that would cover them, so the user can see which single rule replaces how many. Widening a permission is the user's call, so this only reports; it never rewrites.

type Rewrite

type Rewrite struct {
	Rule Rule
	To   string
}

Rewrite replaces a rule's text with a corrected one.

type Rule

type Rule struct {
	Raw    string // exactly as it appears in the file
	Kind   string // allow, deny or ask
	Tool   string // the tool name, or the whole entry when it is a bare name
	Arg    string // the text inside the parentheses
	Bare   bool   // written without parentheses
	Source Source
	Index  int // position within its list, for stable ordering
}

Rule is a single permission entry as written.

func Read

func Read(src Source) []Rule

Read parses one settings file into rules. A file that does not exist or does not parse yields nothing rather than an error: a broken project file should not stop the audit of every other one.

type Scope

type Scope int

Scope is where a settings file sits in Claude Code's precedence order. Managed settings win over everything, including command line arguments.

const (
	ScopeUser Scope = iota
	ScopeShared
	ScopeLocal
	ScopeManaged
)

func (Scope) String

func (s Scope) String() string

type Source

type Source struct {
	Path  string
	Scope Scope
	Repo  string // the project the file belongs to, empty for user and managed
}

Source is one settings file that contributed rules.

func Discover

func Discover(claudeDir string, repos []string) []Source

Discover finds every settings file that carries permission rules: the managed file, the user file, and the shared and local files of each repo passed in.

func (Source) Short

func (s Source) Short() string

Short renders a source path for display, collapsing the home directory.

Jump to

Keyboard shortcuts

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