audit

package
v0.78.0 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const MaxQueryLimit = 10000

MaxQueryLimit caps the per-call row count returned by Query and QueryFiltered. The audit DB grows without bound across sessions; a caller asking for limit=1000000 (operator typo, malicious LLM tool call, stress test) would tie up SQLite for seconds and flood downstream consumers. 10k is generous for any reasonable triage flow — callers wanting more should paginate via Offset.

Both the REPL slash commands (/audit query, /history) and the audit_query tool consult this cap so the cap can't be bypassed by routing through a different surface.

Variables

This section is empty.

Functions

func RequireOpen added in v0.17.0

func RequireOpen(l *Log, level risk.Level) error

RequireOpen returns an error when l is nil and level is High or above. This enforces fail-closed behaviour: when the audit log is not initialised, destructive (High/Critical) actions are refused rather than proceeding silently. Low/Medium actions are permitted without an audit log for back-compat.

Types

type Entry

type Entry struct {
	ID        int64     `json:"id"`
	Timestamp time.Time `json:"timestamp"`
	Tool      string    `json:"tool"`
	Input     string    `json:"input"`
	Output    string    `json:"output"`
	Risk      string    `json:"risk"`
	Level     Level     `json:"level"`
	SessionID string    `json:"session_id"`
	Duration  int64     `json:"duration_ms"`
	Success   bool      `json:"success"`

	// TraceID correlates this entry with one REPL turn. Not persisted to
	// the DB (schema predates the field); carried in-memory so observers
	// (rules engine, webhooks, slog) can surface the turn that produced
	// it. Empty when the caller did not route through obs.WithTrace.
	TraceID string `json:"trace_id,omitempty"`

	// TechniqueIDs records the MITRE ATT&CK technique IDs the tool
	// contributes to at recording time (P1-07). Populated by the
	// agent from the attack.Index; derived, not persisted to the
	// DB schema. Enables the /report ATT&CK coverage heatmap to
	// trust entry-time mappings even if the index changes later.
	TechniqueIDs []string `json:"technique_ids,omitempty"`

	// PersonaVersion is the operator-supplied version string from the
	// active persona's `version:` YAML field at recording time
	// (P3-31). Populated via the per-session PersonaContextResolver.
	// Carried in-memory only; not persisted to the DB schema. Empty
	// when the operator hasn't versioned the persona, when no
	// persona is active, or when the resolver is unset.
	PersonaVersion string `json:"persona_version,omitempty"`

	// PromptHash is the SHA-256 (hex) of the system prompt the agent
	// would have presented for this turn (P3-31). Same provenance
	// rules as PersonaVersion. Lets a regression analyser group
	// sessions by exact prompt content even if the persona version
	// string didn't change (e.g. a prompt typo fixed without bumping
	// the version).
	PromptHash string `json:"prompt_hash,omitempty"`
}

type Filter

type Filter struct {
	Tool     string    // substring match on tool name (LIKE '%<v>%')
	Risk     string    // exact: low|medium|high|critical
	Session  string    // exact session id
	Since    time.Time // timestamp >= Since when non-zero
	Until    time.Time // timestamp <= Until when non-zero
	Success  *bool     // nil = any; &true / &false to filter
	Contains string    // substring match on input OR output
	Limit    int       // default 100 when <= 0
	Offset   int       // rows to skip for pagination
}

Filter is the declarative query shape accepted by QueryFiltered. Zero fields are ignored; non-zero fields are ANDed together. All string matches against indexed columns are exact (Risk, Session) or substring (Tool, Contains); none use user-supplied SQL fragments.

type Level

type Level string
const (
	LevelInfo     Level = "info"
	LevelAction   Level = "action"
	LevelWarning  Level = "warning"
	LevelCritical Level = "critical"
)

type Log

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

func Open

func Open(dbPath string) (*Log, error)

Open prepares the audit log at dbPath. It takes a non-blocking advisory flock on the db file so only one PromptZero process writes to a given path at a time; if that lock is already held, Open falls back to a PID-suffixed sibling path (<dbPath>.<pid>) and logs a warning. The fallback keeps the REPL responsive instead of hard-erroring when a stale or sibling process still holds the primary log — each process gets its own WAL-backed sqlite file and concurrent writers no longer corrupt each other.

The lock is released on Log.Close.

func (*Log) AddObserver

func (l *Log) AddObserver(fn func(Entry))

AddObserver registers a callback fired after every successful Record insert. Observers run synchronously on the caller goroutine, so keep them fast — for anything network-bound (webhooks) the observer should enqueue and return immediately. Adding during iteration is safe; observers added mid-notify are picked up on the next event.

func (*Log) Close

func (l *Log) Close() error

func (*Log) Export

func (l *Log) Export() (string, error)

func (*Log) MaxID

func (l *Log) MaxID() (int64, error)

MaxID returns the current highest row id, used by the /audit tail implementation to watch for new inserts.

func (*Log) Path

func (l *Log) Path() string

Path returns the on-disk path of the sqlite db backing this log. When the primary path was contended at Open time this will be the PID-suffixed fallback; tests and /audit tail use it to distinguish the two cases.

func (*Log) Query

func (l *Log) Query(limit int) ([]Entry, error)

func (*Log) QueryBySession

func (l *Log) QueryBySession(sessionID string) ([]Entry, error)

func (*Log) QueryFiltered

func (l *Log) QueryFiltered(f Filter) ([]Entry, error)

QueryFiltered returns audit entries matching f. All user-supplied values are bound as SQL parameters — no string interpolation, so operator input cannot inject SQL. An empty Filter returns the most recent 100 entries.

func (*Log) QuerySince

func (l *Log) QuerySince(afterID int64) ([]Entry, error)

QuerySince returns entries whose id is strictly greater than afterID, ordered oldest-first. Pair with MaxID() to tail new audit rows.

func (*Log) Record

func (l *Log) Record(tool string, input interface{}, output string, risk string, level Level, duration time.Duration, success bool)

func (*Log) RecordCtx

func (l *Log) RecordCtx(ctx context.Context, tool string, input interface{}, output string, risk string, level Level, duration time.Duration, success bool)

RecordCtx is the ctx-aware Record path. When ctx carries a trace (via obs.WithTrace) the trace ID is attached to the emitted Entry and the structured log line so observers can correlate the audit row with the REPL turn that produced it.

func (*Log) SessionID

func (l *Log) SessionID() string

func (*Log) SetPersonaContextResolver added in v0.53.0

func (l *Log) SetPersonaContextResolver(fn PersonaContextResolver)

SetPersonaContextResolver installs the per-session hook used to populate Entry.PersonaVersion + Entry.PromptHash on each audit row (P3-31). Pass nil to disable. The same race-tolerance contract as SetTechniqueResolver — call once at agent startup; mid-session persona switches simply update the closure the agent installs.

func (*Log) SetTechniqueResolver added in v0.3.0

func (l *Log) SetTechniqueResolver(fn TechniqueResolver)

SetTechniqueResolver installs the ATT&CK tool-to-technique mapping used to populate Entry.TechniqueIDs. Pass nil to disable. Safe to call from setup code before Record begins; callers during Record accept the race (the next entry picks up the new resolver).

func (*Log) Stats

func (l *Log) Stats() (string, error)

func (*Log) TopRisks

func (l *Log) TopRisks(since time.Time) ([]RiskCount, error)

TopRisks groups audit entries by risk level and returns the count-desc ordering. Used by /audit top risks to spotlight whether a session leans heavy on destructive calls.

func (*Log) TopTools

func (l *Log) TopTools(since time.Time, n int) ([]ToolCount, error)

TopTools groups audit entries by tool and returns the count-desc top-n, optionally restricted to entries since the given time. A zero since means "all time".

type PersonaContext added in v0.53.0

type PersonaContext struct {
	PersonaVersion string
	PromptHash     string
}

PersonaContext is the per-session prompt + persona snapshot recorded on every audit row (P3-31). Populated by the agent at session start and on persona-switch; the audit log reads it on each Record so regression analysis can group rows by the exact prompt content the operator was running.

type PersonaContextResolver added in v0.53.0

type PersonaContextResolver func() PersonaContext

PersonaContextResolver is the hook the agent installs so the audit log can pick up the active PersonaContext at record time. The resolver is invoked once per audit row; nil resolver leaves the fields empty.

type RiskCount

type RiskCount struct {
	Risk  string
	Count int
}

RiskCount is one row of a top-risks aggregation.

type TechniqueResolver added in v0.3.0

type TechniqueResolver func(toolName string) []string

TechniqueResolver is an optional hook that maps a tool name to the ATT&CK technique IDs it contributes to. Installed via SetTechniqueResolver; wired at agent startup to internal/attack's Index (P1-07). Empty slice / nil resolver means entries carry no TechniqueIDs.

type ToolCount

type ToolCount struct {
	Tool  string
	Count int
}

ToolCount is one row of a top-tools aggregation.

Jump to

Keyboard shortcuts

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