policy

package
v0.2.6 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 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. A tainted write or exec that would normally run is escalated to ask because the instructions may no longer be entirely the user's. A deny rule always wins. An allow rule is re-confirmed after taint because approving a trusted writer in clean sessions does not let injected content drive it.

func (*Engine) External added in v0.2.5

func (e *Engine) External(name string) bool

External reports whether name belongs to code outside tomo's reviewed tool surface. The guard uses this after a successful call because an external tool can return injected content regardless of its declared capability class.

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(name string, class tool.Class, isErr bool)

Ingested updates taint after a tool ran. A successful network call or any external tool result introduces untrusted content, so later writes and exec calls escalate to approval. Externality is keyed by the registered tool name instead of trusting a third party to classify itself as net.

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