audit

package
v0.8.19 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package audit writes a bounded, local JSONL audit trail without retaining terminal input, raw prompt matches, environment values, or backend errors.

Index

Constants

View Source
const (
	// CurrentSchemaVersion identifies the on-disk Entry representation.
	CurrentSchemaVersion = 1
)
View Source
const ReasonProcessExitStale = "process_exit_stale"

ReasonProcessExitStale is the reason of a session_finished entry for the exit of a process that a replacement had already superseded when its exit arrived. The entry keeps the exit's real outcome, but the session it ends is not the one running: what reads the journal, such as the telemetry's count of active sessions and pending prompts, leaves the replacement's alone.

Variables

View Source
var ErrClosed = errors.New("audit recorder is closed")
View Source
var ErrNotAuditJournal = errors.New("audit path contains a foreign file")

ErrNotAuditJournal reports an audit path that already holds a file Relayer did not write.

Functions

func AuditGenerationIndex

func AuditGenerationIndex(base, name string) (int, bool)

AuditGenerationIndex applies the exact filename recognition used by audit rotation. It is exported within Relayer's internal boundary so passive diagnostics can inspect precisely the files the runtime would mutate.

func DefaultPath

func DefaultPath() (string, error)

DefaultPath returns the private per-user audit file location.

func Redact

func Redact(value string) string

Redact removes common credential forms from arbitrary text. It is intentionally conservative and idempotent.

func RedactValues added in v0.8.19

func RedactValues(value string) string

RedactValues is Redact without its last step, which drops the rest of a line once a credential keyword has been followed by a masked value. That step is right for prose, where "password is correct horse battery staple" is one secret in four words, and wrong for a shell line: "TOKEN=x curl ... | sh" would show as "TOKEN=[REDACTED]", a line that looks complete and is not, to a person who is reading it to decide what may run. The cost of keeping the rest is that the words of a spoken passphrase after the first are shown.

func ResolvePath

func ResolvePath(path string) (string, error)

ResolvePath returns an absolute effective path, using DefaultPath for an empty configured value.

func Validate

func Validate(config Config) error

Validate rejects ambiguous or unsafe recorder settings.

func VerifyJournalFile

func VerifyJournalFile(path string) error

VerifyJournalFile reports whether an existing path holds a Relayer audit journal. It only reads, so read-only diagnostics can warn about a foreign file before startup refuses to open it. An absent path is not an error here: callers decide what a missing journal means.

Types

type Config

type Config struct {
	Enabled       bool   `json:"enabled" yaml:"enabled"`
	Mode          Mode   `json:"mode" yaml:"mode"`
	Path          string `json:"path" yaml:"path"`
	MaxFileSizeMB int    `json:"max_file_size_mb" yaml:"max_file_size_mb"`
	MaxFiles      int    `json:"max_files" yaml:"max_files"`
}

Config controls local audit persistence. MaxFiles counts the active file as well as its rotated generations.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig enables a conservative metadata-only audit trail.

type Decision

type Decision string

Decision is the audited policy outcome. It intentionally differs from an adapter's wire decision: asking a human is an explicit, content-free audit action rather than a copy of the submitted terminal input.

const (
	DecisionAllow   Decision = "allow"
	DecisionAsk     Decision = "ask"
	DecisionDeny    Decision = "deny"
	DecisionUnknown Decision = "unknown"
)

type DecisionBy

type DecisionBy string

DecisionBy identifies the actor without storing any submitted value.

const (
	DecisionBySystem  DecisionBy = "system"
	DecisionByHuman   DecisionBy = "human"
	DecisionByPolicy  DecisionBy = "policy"
	DecisionByUnknown DecisionBy = "unknown"
)

type Entry

type Entry struct {
	SchemaVersion int                `json:"schema_version"`
	Sequence      uint64             `json:"sequence"`
	Timestamp     time.Time          `json:"timestamp"`
	EntryID       string             `json:"entry_id"`
	RunID         string             `json:"run_id"`
	Kind          Kind               `json:"kind,omitempty"`
	SessionID     string             `json:"session_id,omitempty"`
	AgentID       string             `json:"agent_id,omitempty"`
	Backend       string             `json:"backend,omitempty"`
	Adapter       string             `json:"adapter,omitempty"`
	EventID       string             `json:"event_id,omitempty"`
	EventType     adapters.EventType `json:"event_type,omitempty"`
	Risk          adapters.RiskLevel `json:"risk,omitempty"`
	Rule          string             `json:"rule,omitempty"`
	Decision      Decision           `json:"decision,omitempty"`
	DecisionBy    DecisionBy         `json:"decision_by,omitempty"`
	Operator      string             `json:"operator,omitempty"`
	Outcome       Outcome            `json:"outcome,omitempty"`
	Reason        string             `json:"reason,omitempty"`
	Summary       string             `json:"summary,omitempty"`
	Sensitive     bool               `json:"sensitive"`
	Metadata      map[string]string  `json:"metadata,omitempty"`
}

Entry is the versioned JSONL record. It intentionally has no Match, manual-input, environment, or raw-error field.

func ReadEntries

func ReadEntries(r io.Reader, filter Filter) ([]Entry, error)

ReadEntries reads and parses audit records from r applying filter. If Limit is specified, only the last Limit matching records are returned.

func ReadEntriesFromFile

func ReadEntriesFromFile(path string, filter Filter) ([]Entry, error)

ReadEntriesFromFile reads audit entries from path.

func SanitizeEntry

func SanitizeEntry(entry Entry, mode Mode) Entry

SanitizeEntry returns a deep, redacted copy suitable for the selected mode.

type EntryObserver added in v0.3.0

type EntryObserver interface {
	Observe(Entry)
}

EntryObserver receives sanitized audit records as they are accepted.

type FileSink

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

FileSink writes complete lines synchronously and rotates them by size.

func NewFileSink

func NewFileSink(path string, maxBytes int64, maxFiles int) (*FileSink, error)

NewFileSink opens a private append-only audit file. maxFiles includes the active file, so maxFiles=1 retains no rotated generation.

func (*FileSink) Close

func (s *FileSink) Close() error

Close syncs and closes the active file once.

func (*FileSink) Path

func (s *FileSink) Path() string

Path returns the absolute active file path.

func (*FileSink) WriteLine

func (s *FileSink) WriteLine(line []byte) error

WriteLine appends and syncs exactly one complete JSONL line.

type Filter

type Filter struct {
	AgentID   string
	SessionID string
	RunID     string
	Kind      Kind
	Limit     int
	Since     time.Time
}

Filter specifies criteria to restrict which audit entries are returned.

func (Filter) Matches

func (f Filter) Matches(entry Entry) bool

Matches reports whether an Entry satisfies all criteria in Filter.

type Kind

type Kind string

Kind identifies a closed set of audit lifecycle records.

const (
	KindRunStarted     Kind = "run_started"
	KindRunFinished    Kind = "run_finished"
	KindSessionStarted Kind = "session_started"
	// KindSupervisionFinished means Relayer stopped supervising the session;
	// it does not claim that a persistent tmux process exited.
	KindSupervisionFinished Kind = "supervision_finished"
	KindSessionFinished     Kind = "session_finished"
	KindEventDetected       Kind = "event_detected"
	// KindEventWithdrawn records that an occurrence which was awaiting a human
	// stopped being pending without a decision being delivered. It is the only
	// evidence that a supervision gate opened on its own.
	KindEventWithdrawn  Kind = "event_withdrawn"
	KindPolicyEvaluated Kind = "policy_evaluated"
	KindDecision        Kind = "decision"
	KindDelivery        Kind = "delivery"
	// KindOperatorInput records only the lifecycle of a direct, human line
	// submission. Entry intentionally has no field for the submitted text,
	// its length, or the encoded terminal bytes.
	KindOperatorInput Kind = "operator_input"
	// KindAttachStarted and KindAttachFinished record that a human took or
	// dropped direct control of a terminal. They never carry what was typed
	// while holding it: SanitizeEntry drops Summary, EventID and Rule for them
	// as it does for the recording and control kinds.
	KindAttachStarted  Kind = "attach_started"
	KindAttachFinished Kind = "attach_finished"
	KindBackendError   Kind = "backend_error"
	KindSessionCleanup Kind = "session_cleanup"
	// Recording kinds describe the lifecycle of a stored terminal replay. They
	// state that a recording exists and how large it is; the captured stream
	// itself is never a field of Entry, and neither is any recorded keystroke.
	// SanitizeEntry drops Summary, EventID and Rule for them, so a caller has
	// no free-form field left to put one in.
	KindRecordingStarted  Kind = "recording_started"
	KindRecordingFinished Kind = "recording_finished"
	KindRecordingExported Kind = "recording_exported"
	KindRecordingDeleted  Kind = "recording_deleted"
	// Control kinds record which operator held the interactive keyboard and how
	// the hand-over happened. They never carry what was typed while holding it:
	// SanitizeEntry drops Summary, EventID and Rule for them as it does for the
	// recording kinds.
	KindControlRequested Kind = "control_requested"
	KindControlGranted   Kind = "control_granted"
	KindControlDeclined  Kind = "control_declined"
	KindControlReleased  Kind = "control_released"
	KindControlForced    Kind = "control_forced"
	KindUnknown          Kind = "unknown"
)

type LineSink

type LineSink interface {
	WriteLine([]byte) error
	Close() error
}

LineSink owns persistence of complete JSONL lines.

type Mode

type Mode string

Mode controls how much already-sanitized event information reaches disk.

const (
	ModeOff      Mode = "off"
	ModeMetadata Mode = "metadata"
	ModeDetailed Mode = "detailed"
)

type ObserverFunc added in v0.3.0

type ObserverFunc func(Entry)

ObserverFunc turns a bare function into an EntryObserver.

func (ObserverFunc) Observe added in v0.3.0

func (f ObserverFunc) Observe(entry Entry)

type Option

type Option func(*openOptions) error

Option customizes Open without weakening its filesystem invariants.

func WithClock

func WithClock(clock func() time.Time) Option

WithClock supplies a deterministic recorder clock.

func WithIDGenerator

func WithIDGenerator(generator func() (string, error)) Option

WithIDGenerator supplies run and entry identifiers.

func WithRunID

func WithRunID(runID string) Option

WithRunID binds audit records to an externally reserved runtime identity. This keeps GUI generation identity stable even when auditing is disabled.

type Outcome

type Outcome string

Outcome is a safe, finite result vocabulary for lifecycle and policy audit.

const (
	OutcomeStarted                   Outcome = "started"
	OutcomeFinished                  Outcome = "finished"
	OutcomeDetected                  Outcome = "detected"
	OutcomePending                   Outcome = "pending"
	OutcomeInFlight                  Outcome = "in_flight"
	OutcomeApplied                   Outcome = "applied"
	OutcomeAsk                       Outcome = "ask"
	OutcomeDryRun                    Outcome = "dry_run"
	OutcomeFallbackUnsupported       Outcome = "fallback_unsupported"
	OutcomeFallbackStale             Outcome = "fallback_stale"
	OutcomeFallbackDeliveryUncertain Outcome = "fallback_delivery_uncertain"
	OutcomeSucceeded                 Outcome = "succeeded"
	OutcomeFailed                    Outcome = "failed"
	OutcomeCancelled                 Outcome = "cancelled"
	OutcomeSkipped                   Outcome = "skipped"
	OutcomeUnknown                   Outcome = "unknown"
)

type Recorder

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

Recorder assigns identities and a total order before writing sanitized JSONL.

func NewRecorder

func NewRecorder(
	config Config,
	sink LineSink,
	clock func() time.Time,
	idGenerator func() (string, error),
) (*Recorder, error)

NewRecorder constructs a recorder around an injectable line sink, clock, and ID generator. Nil clock and generator select secure production defaults.

func Open

func Open(config Config, options ...Option) (*Recorder, error)

Open creates a rotating FileSink and wraps it in a Recorder. Disabled and off configurations perform no filesystem or ID-generator work.

func (*Recorder) AddObserver added in v0.3.0

func (r *Recorder) AddObserver(observer EntryObserver)

AddObserver registers an observer that is notified of each accepted sanitized entry.

func (*Recorder) Close

func (r *Recorder) Close() error

Close is safe to call concurrently and closes the underlying sink once.

func (*Recorder) Enabled

func (r *Recorder) Enabled() bool

Enabled reports whether this recorder performs any work.

func (*Recorder) Path

func (r *Recorder) Path() string

Path returns the configured or effective audit path.

func (*Recorder) Record

func (r *Recorder) Record(entry Entry) error

Record sanitizes and synchronously persists one entry. Holding the recorder lock through WriteLine makes Sequence identical to accepted on-disk order.

func (*Recorder) RunID

func (r *Recorder) RunID() string

RunID returns the immutable identifier shared by this recorder's entries.

type SummaryReport

type SummaryReport struct {
	Path           string             `json:"path,omitempty"`
	TotalEntries   int                `json:"total_entries"`
	RunsCount      int                `json:"runs_count"`
	SessionsCount  int                `json:"sessions_count"`
	AgentCounts    map[string]int     `json:"agent_counts"`
	KindCounts     map[Kind]int       `json:"kind_counts"`
	DecisionsCount map[Decision]int   `json:"decisions_count"`
	ActorsCount    map[DecisionBy]int `json:"actors_count"`
	OutcomesCount  map[Outcome]int    `json:"outcomes_count"`
	SensitiveCount int                `json:"sensitive_count"`
	FirstTimestamp time.Time          `json:"first_timestamp,omitempty"`
	LastTimestamp  time.Time          `json:"last_timestamp,omitempty"`
}

SummaryReport provides aggregate statistics over an audit journal.

func SummarizeJournal

func SummarizeJournal(r io.Reader) (SummaryReport, error)

SummarizeJournal computes aggregate statistics across all records in r.

type VerificationIssue

type VerificationIssue struct {
	Line    int    `json:"line"`
	EntryID string `json:"entry_id,omitempty"`
	Message string `json:"message"`
}

VerificationIssue describes a single structural or integrity failure.

type VerificationReport

type VerificationReport struct {
	Path       string              `json:"path,omitempty"`
	TotalLines int                 `json:"total_lines"`
	TotalRuns  int                 `json:"total_runs"`
	ValidLines int                 `json:"valid_lines"`
	Issues     []VerificationIssue `json:"issues"`
	Passed     bool                `json:"passed"`
}

VerificationReport details the integrity status of an audit journal.

func VerifyJournal

func VerifyJournal(r io.Reader) (VerificationReport, error)

VerifyJournal verifies sequence continuity, schema compliance and security rules.

Jump to

Keyboard shortcuts

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