Documentation
¶
Overview ¶
Package permissions implements the central permission gate that decides whether each tool invocation may proceed.
The gate consults, in order:
- Bash denylist (built-in patterns; non-overridable) for bash calls.
- Path scope check for file tools.
- Config denylist patterns.
- Config allowlist patterns.
- Mode-specific resolution: ask → prompt user; allow → deny; yolo → approve.
The interactive prompt path is implemented by the host (TUI / CLI REPL); see prompter.go for the Prompter interface.
Index ¶
- Variables
- func IsBashDenied(command string) (denied bool, reason string)
- func SortRecommendations(recs []Recommendation)
- type ApprovalLog
- type Decision
- type Gate
- func (g *Gate) Approvals() []ApprovalLog
- func (g *Gate) CheckBash(ctx context.Context, command string) error
- func (g *Gate) CheckFileRead(ctx context.Context, toolName, path string) error
- func (g *Gate) CheckFileWrite(ctx context.Context, toolName, path string) error
- func (g *Gate) CheckGeneric(ctx context.Context, toolName, key string) error
- func (g *Gate) HasPrompter() bool
- func (g *Gate) Mode() Mode
- func (g *Gate) Scope() *PathScope
- type Mode
- type Options
- type Outcome
- type PathScope
- type Policy
- type PromptKind
- type PromptRequest
- type Prompter
- type Recommendation
Constants ¶
This section is empty.
Variables ¶
var ErrNoPrompter = errors.New("permissions: interactive approval required but no prompter is configured")
ErrNoPrompter is returned when the gate would prompt but no prompter is configured (e.g. headless mode without an explicit allowlist).
Functions ¶
func IsBashDenied ¶
IsBashDenied reports whether command matches any built-in denylist pattern. The reason is a short, user-facing string suitable for surfacing in a prompt or stderr.
func SortRecommendations ¶
func SortRecommendations(recs []Recommendation)
SortRecommendations returns a stable ordering: more specific patterns (without `*`) first, then by Pattern lex order.
Types ¶
type ApprovalLog ¶
ApprovalLog is one entry in the gate's per-session approval audit. It records every interactive permission decision the user made (excluding denials) so callers can later offer a "review approvals + recommend" workflow.
type Decision ¶
type Decision int
Decision is the user's choice in an interactive permission prompt.
const ( DecisionDeny Decision = iota // reject this call DecisionAllowOnce // allow this call, ask again next time DecisionAllowSession // allow this exact request for the rest of the session DecisionAllowSessionTool // allow EVERY call to this tool for the rest of the session, regardless of args DecisionAllowAlways // persist a permanent allowlist entry, then allow )
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate is the central permission chokepoint consulted before each tool call. It holds the configured policy, the path scope, the bash denylist (built-in), and an optional Prompter for interactive use.
Gate is safe for concurrent use; tool handlers run in the agent's event-iteration goroutine, but the prompter call may yield while waiting for the user.
func FromConfig ¶
FromConfig builds a Gate from a Config plus the resolved project root and user-global root. The Prompter is wired separately since it depends on whether we're running interactively or headless.
func New ¶
New builds a Gate from the supplied options. The Mode defaults to "ask"; missing Policy/Scope default to permissive empties.
func (*Gate) Approvals ¶
func (g *Gate) Approvals() []ApprovalLog
Approvals returns a defensive copy of the in-session approval log. Order is chronological. Safe for concurrent callers.
func (*Gate) CheckBash ¶
CheckBash gates a bash invocation. The denylist is checked first and is non-overridable. After that, policy + mode determine whether the call needs a prompt.
func (*Gate) CheckFileRead ¶
CheckFileRead gates a read-only file operation. Read access only fails if the path is out of scope (and the user can't or won't extend scope).
func (*Gate) CheckFileWrite ¶
CheckFileWrite gates a mutating file operation. Out-of-scope paths are escalated via prompt; in-scope paths still go through mode-aware approval (ask mode prompts; allow/yolo proceed unless deny rule hits).
func (*Gate) CheckGeneric ¶
CheckGeneric gates an arbitrary tool call (used by MCP and skill toolsets, where we don't have a dedicated Check<Tool> method).
toolName is the namespace under which policy lookups happen (typically "mcp" or "skill"); key is the human-readable detail shown in prompts (typically the tool's full namespaced name plus a brief argument summary).
func (*Gate) HasPrompter ¶
HasPrompter reports whether an interactive Prompter is wired. False means an ask-mode call would fail with ErrNoPrompter rather than reach a human — useful for callers (e.g. autonomous drivers) that want to fail fast at startup instead of on the first tool call.
type Mode ¶
type Mode string
Mode mirrors the permission modes recognized by config.PermissionsConfig.
type Options ¶
type Options struct {
Mode Mode
Policy *Policy
Scope *PathScope
Prompter Prompter // nil = no interactive path; ask-mode unresolved → deny
}
Options configures a Gate at construction time. All fields are optional; sensible defaults apply when omitted.
type Outcome ¶
type Outcome int
Outcome is the result of consulting the policy. It is used by the gate to decide what to do next; it is not the final say (the gate also consults the bash denylist, the path scope, and the mode).
type PathScope ¶
type PathScope struct {
// contains filtered or unexported fields
}
PathScope restricts file tool access to a defined set of paths: the project root, the user-home root, and any explicit pattern in path_scope.allow. Out-of-scope access escalates to a prompt (in interactive mode) or fails immediately (in headless without a prompter).
Patterns supported in the allow list:
- Exact absolute paths.
- Directory trees ending with `/...` (e.g. "/etc/myapp/...").
- Standard filepath glob patterns (passed through path/filepath.Match).
func NewPathScope ¶
NewPathScope constructs a scope from the project root, the user-global home, and an extra allowlist of patterns.
projectRoot may be empty, in which case only the user root and allowlist apply. userRoot may be empty for tests.
func (*PathScope) AddAlwaysAllow ¶
AddAlwaysAllow appends a pattern to the in-memory allowlist. The caller is responsible for persisting the change to config.json.
type Policy ¶
type Policy struct {
// contains filtered or unexported fields
}
Policy interprets the allow/deny pattern lists from .agents/config.json's `permissions` block.
Pattern grammar:
<tool>:<glob> — applies only when the request is for <tool> <glob> — applies to any tool (matched against request key)
`<glob>` is matched with path/filepath.Match, so it understands `*`, `?`, and character classes. The "key" of a request depends on the tool: for bash it is the command string, for file tools it is the resolved absolute path. Wildcards work the same for both.
type PromptKind ¶
type PromptKind int
PromptKind classifies what the gate is asking the user about.
const ( PromptKindBash PromptKind = iota // mutating shell command PromptKindFileWrite // file write/edit/create PromptKindPathScope // file access outside the in-scope roots PromptKindGeneric // anything else )
type PromptRequest ¶
type PromptRequest struct {
Kind PromptKind
ToolName string
Detail string // user-facing description (the bash command, the file path, etc.)
PersistTool string // tool name to use when adding to allowlist (e.g. "bash")
PersistKey string // pattern to add to allowlist
}
PromptRequest carries everything the host needs to render a prompt.
The persistence target — what would be written to .agents/config.json if the user picks DecisionAllowAlways — is held in PersistKey/PersistTool so the prompter doesn't have to re-derive it from Detail.
type Prompter ¶
type Prompter interface {
AskApproval(ctx context.Context, req PromptRequest) (Decision, error)
}
Prompter is implemented by hosts that can interact with the user. Headless callers may pass nil; the gate treats a nil prompter as "no interactive path available".
func StdinPrompter ¶ added in v1.1.0
StdinPrompter returns a Prompter that renders permission requests to out and reads a single-character decision key from in. Suitable for interactive CLI use; callers should gate construction on runner.IsTerminal(in) so headless invocations fall back to ErrNoPrompter instead of blocking on stdin.
Decision keys (case-insensitive, one character followed by newline):
y allow once s allow this exact request for the rest of the session t allow every call to this tool for the rest of the session a allow always (persist to the project's config allowlist) n / empty deny
Invalid input reprompts. EOF or context cancellation returns the underlying error so the gate can surface a denial with context.
type Recommendation ¶
type Recommendation struct {
Pattern string // e.g. "bash:git *", "read_file:internal/tui/**"
Reason string // one-line human explanation of what this covers
Evidence []string // sample keys that motivated the recommendation
}
Recommendation describes one suggested allowlist entry the user could persist into .agents/config.json's `permissions.allow` block. Pattern is in the existing tool:glob form so a recommendation can be appended verbatim. Reason and Evidence let a picker explain WHY before the user agrees to broaden their permanent allowlist.
func Recommend ¶
func Recommend(approvals []ApprovalLog) []Recommendation
Recommend turns a session's interactive approval log into a small list of suggested permanent allowlist entries.