policy

package
v0.2.3 Latest Latest
Warning

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

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

Documentation

Overview

Package policy is the fail-closed gate every tool call passes through. A decision comes from three things: the tool's capability class, any per-tool override, and whether the session has ingested untrusted outside content. The default posture is conservative on purpose; this is the difference between an agent that helps and one that runs whatever a fetched web page told it to.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Scrub added in v0.2.2

func Scrub(raw json.RawMessage) json.RawMessage

Scrub returns a copy of a tool input with credential-shaped values replaced by a marker, so the audit log can record what ran without ever storing a secret it carried. A visibility feature that leaks keys is worse than none; this is the boundary that keeps `tomo watch` and audit.log safe to read.

Input that is not valid JSON is returned unchanged: the auditor writes what the gate saw, and we do not have a structure to reason over. The common case, a tool's JSON arguments, is walked field by field.

Types

type Approver

type Approver interface {
	Approve(ctx context.Context, req Request) (bool, error)
}

Approver answers ask decisions. Each channel supplies its own: a y/n prompt on the terminal, inline buttons on Telegram, a click in the web UI.

type Auditor

type Auditor interface {
	Record(Entry)
}

Auditor records what happened. A nil Auditor is fine; the guard skips it.

type Config

type Config struct {
	Read  string            `yaml:"read"`
	Net   string            `yaml:"net"`
	Write string            `yaml:"write"`
	Exec  string            `yaml:"exec"`
	Rules map[string]string `yaml:"rules"`
}

Config is the policy section of the config file. Class defaults set the baseline; Rules override by exact tool name and win over the class default.

type Decision

type Decision string

Decision is what the engine says about a call.

const (
	Allow Decision = "allow" // run it, no questions
	Ask   Decision = "ask"   // run it only if the user approves
	Deny  Decision = "deny"  // never run it
)

func ParseDecision

func ParseDecision(s string) Decision

ParseDecision reads a decision from config, defaulting unknown or empty values to Ask so a typo fails closed rather than open.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine evaluates decisions. The zero value is not useful; build with New.

func New

func New(cfg Config) *Engine

New builds an engine from config, filling any unset class with the safe default: reads and network calls run, writes and code execution ask.

func (*Engine) Decide

func (e *Engine) Decide(name string, class tool.Class, tainted bool) (Decision, string)

Decide returns the decision for one call. tainted is true once the session has pulled in untrusted external content; in that state a write or exec that would normally run is escalated to ask, because the model's instructions may no longer be entirely the user's. A per-tool rule still wins: an explicit allow or deny is the user's considered choice and is not second-guessed.

func (*Engine) MarkExternal

func (e *Engine) MarkExternal(names ...string)

MarkExternal flags tools that come from outside tomo, such as those served by an MCP server, bridged from a CLI, or reached through ant. They default to ask even when their class would normally run, since their code is not tomo's. An explicit per-tool rule still wins, so a user who trusts one can allow it.

type Entry

type Entry struct {
	Time     string          `json:"time"`
	Tool     string          `json:"tool"`
	Class    tool.Class      `json:"class"`
	Input    json.RawMessage `json:"input,omitempty"`
	Decision Decision        `json:"decision"`
	Reason   string          `json:"reason"`
	Approved *bool           `json:"approved,omitempty"`
	Allowed  bool            `json:"allowed"`
	Tainted  bool            `json:"tainted"`
}

Entry is one line of the audit log.

type FileAuditor

type FileAuditor struct {
	// contains filtered or unexported fields
}

FileAuditor appends one JSON object per line to an audit file. Append-only and flushed per write, so the record survives a crash and cannot be quietly rewritten. A write error is dropped rather than crashing the agent; the audit log must never be the thing that takes tomo down.

func OpenFileAuditor

func OpenFileAuditor(path string) (*FileAuditor, error)

OpenFileAuditor opens (creating, appending) the audit log at path.

func (*FileAuditor) Close

func (a *FileAuditor) Close() error

Close releases the file.

func (*FileAuditor) Record

func (a *FileAuditor) Record(e Entry)

Record writes one entry.

type Guard

type Guard struct {
	// contains filtered or unexported fields
}

Guard couples the engine with an approver, an auditor, and the session's taint state. It implements the gate the agent loop calls. Safe for the single-turn use the loop makes of it; the mutex guards taint against a channel updating it concurrently.

func NewGuard

func NewGuard(e *Engine, approver Approver, auditor Auditor) *Guard

NewGuard wires a guard. now may be nil, defaulting to time.Now.

func (*Guard) Allow

func (g *Guard) Allow(ctx context.Context, name string, class tool.Class, input json.RawMessage) (bool, string)

Allow decides whether a call may run, blocking for approval when the policy says ask. It returns the go-ahead and, when refused, a reason to hand back to the model so it can adapt instead of silently failing.

func (*Guard) Ingested

func (g *Guard) Ingested(class tool.Class, isErr bool)

Ingested updates taint after a tool ran. A successful network call is the moment untrusted content enters the session, so from then on writes and exec escalate to approval.

func (*Guard) Tainted

func (g *Guard) Tainted() bool

Tainted reports the current taint state.

type Request

type Request struct {
	Tool   string
	Class  tool.Class
	Input  json.RawMessage
	Reason string // why approval is being asked, from the engine
}

Request is what an approver is asked to decide.

Jump to

Keyboard shortcuts

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