audit

package
v0.1.17 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package audit defines a best-effort, non-blocking audit sink. An Auditor records every tool call (who/when/what/result) for compliance. The async implementation drops events under backpressure and counts them rather than ever blocking or failing the main business flow (invariant I12).

Index

Constants

This section is empty.

Variables

View Source
var ErrNotDurable = errors.New("audit: no durable sink")

ErrNotDurable is RecordSync on an AsyncAuditor without a durable sink.

View Source
var ErrSinkClosed = errors.New("audit: sink closed")

ErrSinkClosed is returned by Record on a closed AsyncAuditor.

Functions

func RedactInput

func RedactInput(input json.RawMessage, sensitive map[string]bool) json.RawMessage

RedactInput removes values for configured sensitive fields and hashes transaction handles before tool input is queued for persistence.

Types

type AsyncAuditor

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

AsyncAuditor pushes events to a bounded queue drained by a background flusher. On overflow it drops and counts (atomic) instead of blocking.

func NewAsyncAuditorOn added in v0.1.13

func NewAsyncAuditorOn(b *bus.Bus, sink Sink, closeSink func() error, queueSize int) *AsyncAuditor

NewAsyncAuditorOn is NewAsyncAuditorWithClose with the flusher a background task of b. On a closed bus the auditor starts closed: it records nothing.

func NewAsyncAuditorWithClose

func NewAsyncAuditorWithClose(sink Sink, closeSink func() error, queueSize int) *AsyncAuditor

NewAsyncAuditorWithClose starts a flusher goroutine and closes the sink (when closeSink is non-nil) after draining. Call Close to drain and stop.

func (*AsyncAuditor) Close

func (a *AsyncAuditor) Close()

Close stops accepting events and waits for the flusher to drain the queue.

func (*AsyncAuditor) Dropped

func (a *AsyncAuditor) Dropped() int64

Dropped returns the count of events dropped due to a full queue.

func (*AsyncAuditor) Durably added in v0.1.15

func (a *AsyncAuditor) Durably(d Durable) *AsyncAuditor

Durably makes a record synchronously through d (its sink, typically) on RecordSync; without it RecordSync fails with ErrNotDurable.

func (*AsyncAuditor) Record

func (a *AsyncAuditor) Record(_ context.Context, e Event) error

Record enqueues an event non-blocking. On a full queue it drops and counts.

func (*AsyncAuditor) RecordSync added in v0.1.15

func (a *AsyncAuditor) RecordSync(ctx context.Context, e Event) error

RecordSync implements Durable through the durable sink: the event is written, and synced, before it returns, past the queue (events queued earlier may reach the sink after it; readers order events by time).

type Auditor

type Auditor interface {
	Record(ctx context.Context, e Event) error
}

Auditor records an Event. It must not block or fail the caller (invariant I12).

type Durable added in v0.1.15

type Durable interface {
	RecordSync(ctx context.Context, e Event) error
}

Durable records an event before returning, and fails when the event was not written: what the SQL escape hatch (execute_sql) needs, as it refuses to run SQL it could not record (fail closed). FileSink is one, and an AsyncAuditor is one when given its sink (Durably).

type Event

type Event struct {
	Time       time.Time `json:"time"`
	DecisionID string    `json:"decisionId,omitempty"`
	Role       string    `json:"role,omitempty"`
	// User and Roles identify a configured user and its roles; Grants lists
	// the covering grant IDs of the authorization decisions.
	User          string          `json:"user,omitempty"`
	Roles         []string        `json:"roles,omitempty"`
	Grants        []string        `json:"grants,omitempty"`
	Entity        string          `json:"entity,omitempty"`
	Action        string          `json:"action,omitempty"`
	Tool          string          `json:"tool"`
	Input         json.RawMessage `json:"input,omitempty"`
	ResultSummary string          `json:"resultSummary,omitempty"`
	Cost          *cost.Plan      `json:"cost,omitempty"`
	Allowed       bool            `json:"allowed"`
	// Code is the stable machine denial code (core/tool Denial.Code) when the
	// call was rejected by a governed path. Empty with Allowed=false means an
	// internal or protocol-level failure that is not reflected to clients.
	Code         string        `json:"code,omitempty"`
	Error        string        `json:"error,omitempty"`
	Duration     time.Duration `json:"-"`
	ReturnedRows int64         `json:"returnedRows"`
	// The SQL escape hatch (execute_sql) records two events per call, Phase
	// "begin" before the statement runs and "end" after, both carrying the
	// datasource, its connection and the statement as it is (never
	// redacted: the hatch has no entity to mask by); "end" adds the rows a
	// write affected. Other tools leave these unset.
	Phase        string `json:"phase,omitempty"`
	Datasource   string `json:"datasource,omitempty"`
	Connection   string `json:"connection,omitempty"`
	SQL          string `json:"sql,omitempty"`
	RowsAffected int64  `json:"rowsAffected,omitempty"`
}

Event records one tool invocation for compliance. DecisionID correlates the event with the MCP response and trace span of the same call.

The JSON field names below are a frozen public contract (see docs/tool-contract.md): adding a new optional field is compatible; renaming or removing a field is breaking. Duration serializes as integer milliseconds under "durationMs". Cost is reserved: its inner structure is not yet part of the frozen contract.

func (Event) MarshalJSON added in v0.1.6

func (e Event) MarshalJSON() ([]byte, error)

MarshalJSON emits Duration as integer milliseconds ("durationMs") so the JSON Lines contract does not depend on Go's nanosecond Duration encoding.

type FileSink

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

FileSink is a long-lived JSON-lines audit sink.

func OpenFileSink

func OpenFileSink(path string) (*FileSink, error)

OpenFileSink opens path with owner-only permissions.

func (*FileSink) Close

func (s *FileSink) Close() error

Close flushes and closes the file.

func (*FileSink) Record

func (s *FileSink) Record(event Event) error

Record appends one event.

func (*FileSink) RecordSync added in v0.1.15

func (s *FileSink) RecordSync(_ context.Context, event Event) error

RecordSync implements Durable: the event is appended and synced to disk before it returns, so an escape hatch statement never runs unrecorded.

type NoopAuditor

type NoopAuditor struct{}

NoopAuditor discards everything.

func (NoopAuditor) Record

func (NoopAuditor) Record(_ context.Context, _ Event) error

Record is a no-op.

type Sink

type Sink func(Event) error

Sink consumes one event. Errors are ignored (best-effort).

Jump to

Keyboard shortcuts

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