Documentation
¶
Index ¶
- Constants
- func RequireOpen(l *Log, level risk.Level) error
- type Entry
- type Filter
- type Level
- type Log
- func (l *Log) AddObserver(fn func(Entry))
- func (l *Log) Close() error
- func (l *Log) Export() (string, error)
- func (l *Log) ExportCSV() (string, error)
- func (l *Log) MaxID() (int64, error)
- func (l *Log) Path() string
- func (l *Log) Query(limit int) ([]Entry, error)
- func (l *Log) QueryBySession(sessionID string) ([]Entry, error)
- func (l *Log) QueryFiltered(f Filter) ([]Entry, error)
- func (l *Log) QuerySince(afterID int64) ([]Entry, error)
- func (l *Log) Record(tool string, input interface{}, output string, risk string, level Level, ...)
- func (l *Log) RecordCtx(ctx context.Context, tool string, input interface{}, output string, ...)
- func (l *Log) SessionID() string
- func (l *Log) SetPersonaContextResolver(fn PersonaContextResolver)
- func (l *Log) SetTechniqueResolver(fn TechniqueResolver)
- func (l *Log) Stats() (string, error)
- func (l *Log) TopRisks(since time.Time) ([]RiskCount, error)
- func (l *Log) TopTools(since time.Time, n int) ([]ToolCount, error)
- type PersonaContext
- type PersonaContextResolver
- type RiskCount
- type TechniqueResolver
- type ToolCount
Constants ¶
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
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 Log ¶
type Log struct {
// contains filtered or unexported fields
}
func Open ¶
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 ¶
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) ExportCSV ¶ added in v0.348.0
ExportCSV returns the current session's audit entries as CSV text suitable for spreadsheet import or SIEM ingestion. Columns match the JSON Export field names. Values containing commas, quotes, or newlines are quoted per RFC 4180.
func (*Log) MaxID ¶
MaxID returns the current highest row id, used by the /audit tail implementation to watch for new inserts.
func (*Log) Path ¶
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) QueryFiltered ¶
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 ¶
QuerySince returns entries whose id is strictly greater than afterID, ordered oldest-first. Pair with MaxID() to tail new audit rows.
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) 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).
type PersonaContext ¶ added in v0.53.0
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 TechniqueResolver ¶ added in v0.3.0
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.