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 ¶
- func Append(root, pattern string, trusted bool) error
- func CommandHead(command string) string
- func EffectiveCommand(command string) string
- func FilePath(root string) string
- func IsSequence(command string) bool
- func RecoverableInRepo(command, cwd, root string) bool
- func Remove(root, pattern string) (bool, error)
- func RiskHead(command string) string
- func RiskSegment(command string) string
- type Approval
- type Decision
- type Mode
- type Risk
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Append ¶
Append adds a rule (idempotent — a duplicate pattern is a no-op), creating the file with its header if needed. One O_APPEND|O_CREATE open + a single write: the old stat-then-create-then-reopen sequence raced concurrent writers (two processes could each "create" and one header/rule got clobbered). The dedupe check reads through the same open handle, so check-and-append can no longer interleave with another process's create.
func CommandHead ¶
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 ¶
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 IsSequence ¶
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 ¶
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 ¶
Remove deletes every rule whose pattern equals pattern, rewriting the file. Returns whether anything was removed.
func RiskHead ¶
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 ¶
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.
type Risk ¶
type Risk int
Risk is how dangerous an action is.
func ClassifyBash ¶
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.