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 ¶
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.
func ParseDecision ¶
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 ¶
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 ¶
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 ¶
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.
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 (*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.