permissions

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package permissions classifies the risk of an action and decides — given the active mode — whether it may run, needs approval, or is blocked. v0 has no remembered approvals; every medium/dangerous action is decided live.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append(root, pattern string, trusted bool) error

Append adds a rule (idempotent — a duplicate pattern is a no-op), creating the file with its header if needed.

func CommandHead

func CommandHead(command string) string

CommandHead is the real binary of a command (basename, after env-assignments + substitutions are stripped) — the token a remembered approval should key on.

func EffectiveCommand

func EffectiveCommand(command string) string

EffectiveCommand strips leading env-assignments (VAR=val, VAR=$(...)), returning the command starting at its REAL binary. Used so the "don't ask again" rule keys on and matches the actual command, not the assignment prefix. It uses the shell parser: the first simple command's first argument is where the binary starts.

func FilePath

func FilePath(root string) string

FilePath is the permissions file for a repo rooted at root.

func IsSequence

func IsSequence(command string) bool

IsSequence reports whether command is a multi-statement SEQUENCE — statements separated by ;, &, or newlines — rather than a single command or pipeline (a | b and a && b are each ONE statement). Parsed with the real shell AST, never by splitting on ";". Used to tell a chained command whose LAST step returned non-zero (a partial success — e.g. a trailing grep that found nothing) apart from a single command that outright failed.

func RecoverableInRepo

func RecoverableInRepo(command, cwd, root string) bool

RecoverableInRepo reports whether command's ENTIRE destructive footprint is file deletions (rm/rmdir) whose every target resolves to a path strictly UNDER root — i.e. the blast radius is inside the repo, where git can restore it. When true, the gate may treat the command like an edit (Medium) instead of an always-confirm catastrophe. It is deliberately CONSERVATIVE: ANY destructive call that isn't an in-repo rm — a disk-level destroyer (dd/mkfs/shred), a remote op (git push --force), a cloud delete, an mv, or an rm whose target is out-of-repo, the repo root itself, its .git, or unresolvable (a glob/variable) — returns false, keeping the catastrophic floor. Pure: it reasons about parsed argv and path containment only — no filesystem or git IO (the caller decides separately whether root is actually a git repo). cwd is where relative targets resolve; pass root when it's unset.

func Remove

func Remove(root, pattern string) (bool, error)

Remove deletes every rule whose pattern equals pattern, rewriting the file. Returns whether anything was removed.

func RiskHead

func RiskHead(command string) string

RiskHead returns the basename of the simple command that DROVE the command's overall risk — the sub-command that actually triggered the approval prompt. For a compound like `echo … && supabase status`, every safe head (echo/cd/cat/ls) is noise; the prompt exists because of `supabase`, so the approval card and any remembered "don't ask again" rule must key on "supabase", not the leading "echo".

It mirrors classifyFile's walk but tracks the head of the highest-risk CallExpr (first one wins on ties, in source order). Falls back to CommandHead — the leading binary — when nothing escalates above Safe or the command can't be parsed, so an all-safe command still keys on its own head.

func RiskSegment

func RiskSegment(command string) string

RiskSegment returns the command text anchored at its highest-risk simple command — the SAME call RiskHead keys a remembered rule on. It mirrors RiskHead's walk (first-on-ties, highest-risk wins) but returns the source from that call's first arg onward, so a rule saved from a compound (`echo … && supabase …` → "supabase *") can match the command that produced it. Anchoring at the *highest*-risk call is the safety invariant: the pattern must cover the riskiest sub-command (anything earlier is lower-risk; anything later is swallowed by the rule's trailing '*'), and Match's catastrophic guard still forces a re-prompt regardless. Falls back to the whole command when it can't be parsed or has no simple command.

Types

type Approval

type Approval struct {
	ID        string
	Pattern   string
	Cwd       string // "" = any cwd
	Trusted   bool   // may approve catastrophic commands
	ExpiresAt *time.Time
}

Approval is a remembered allow-rule (the matching view of a stored permission). It is intentionally decoupled from the storage layer.

func Load

func Load(root string) ([]Approval, error)

Load parses the permissions file into approval rules. A missing file means no rules (not an error). Malformed lines are skipped, not fatal.

func Match

func Match(approvals []Approval, command, cwd string, catastrophic bool, now time.Time) (Approval, bool)

Match reports whether any approval permits the command (in cwd) at time now. catastrophic commands match only a Trusted rule. It returns the matched approval for bookkeeping.

type Decision

type Decision int

Decision is the gate's verdict for an action.

const (
	Allow      Decision = iota // run without prompting
	NeedPrompt                 // ask the human first
	Block                      // never (catastrophic in a non-interactive context)
)

func Decide

func Decide(mode Mode, risk Risk, catastrophic bool) Decision

Decide returns whether an action of the given risk may proceed under mode. catastrophic marks irreversible commands that always require confirmation, even in allow-all.

type Mode

type Mode string

Mode is the permission policy for a run.

const (
	ModeAsk      Mode = "ask"       // prompt before medium/dangerous (default)
	ModeAuto     Mode = "auto"      // run safe+medium; prompt for dangerous
	ModeAllowAll Mode = "allow-all" // run everything except catastrophic (still prompts those)
)

type Risk

type Risk int

Risk is how dangerous an action is.

const (
	Safe      Risk = iota // read-only / inspection
	Medium                // writes, installs, builds, tests
	Dangerous             // destructive / irreversible / external side effects
)

func ClassifyBash

func ClassifyBash(command string) (Risk, bool)

ClassifyBash returns the risk of a shell command and whether it is catastrophic (irreversible / never auto-approved). It classifies by INTENT — subcommand verbs for cloud CLIs, the SQL verb for DB clients, and recursively for wrappers (sudo/ssh/bash -c/docker exec) — so `gcloud … list` and `psql -c "SELECT"` read as Safe while `gcloud … delete` and `psql -c "DELETE"` read as Dangerous. Anything not positively recognized as read-only lands at Medium or higher so it prompts.

The command is parsed with a real shell parser (mvdan.cc/sh) and classified by walking the AST, so quoting, pipelines, redirects, command substitutions, and subshells are understood structurally rather than guessed at with string surgery.

func (Risk) String

func (r Risk) String() string

Jump to

Keyboard shortcuts

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