audit

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package audit emits one event per terminal gateway response — including 401s, 403s, and 429s — to configured sinks (stdout, webhook).

Index

Constants

View Source
const (
	OutcomeDelivered    = "delivered"
	OutcomeRetried      = "retried"       // an attempt failed and will be tried again
	OutcomeDeadLettered = "dead_lettered" // delivery abandoned, written to the dead-letter file
	OutcomeDropped      = "dropped"       // abandoned with nowhere to put it, or the buffer was full
)

Delivery outcomes reported to the observer. Audit is the single exit door, so the interesting number is not how many events were written but how many were not: a sink that quietly drops is indistinguishable from a quiet system.

Variables

This section is empty.

Functions

This section is empty.

Types

type Event

type Event struct {
	Time      time.Time `json:"time"`
	Principal string    `json:"principal,omitempty"` // subject, or "" when auth is disabled
	Issuer    string    `json:"issuer,omitempty"`
	Method    string    `json:"method"`             // MCP method, e.g. "tools/call"
	Name      string    `json:"name,omitempty"`     // namespaced tool/prompt name
	Upstream  string    `json:"upstream,omitempty"` // routed upstream id
	// Direction is set only on the reverse path — "server_initiated", for the
	// sampling and elicitation requests an upstream makes of the caller's
	// client. Its absence means the ordinary client-to-upstream direction, so
	// every event fold emitted before this field existed reads unchanged.
	Direction string `json:"direction,omitempty"`
	// Tenant is the group the principal resolved to, when tenancy is
	// configured. Empty means no tenant matched — which is not an error, just
	// a caller governed by the gateway-wide rules.
	Tenant string `json:"tenant,omitempty"`
	// Decision is "allow" | "deny" for policy-gated invocations; for
	// oauth/token events it carries the exchange outcome verbatim
	// ("minted", "replayed", "invalid_grant", ...) so a detected replay is
	// alertable on a structured field.
	Decision string `json:"decision,omitempty"`
	RuleID   string `json:"ruleId,omitempty"`

	// MissingScopes names the scopes a denial said would have satisfied a
	// rule that otherwise granted the invocation. Present only on denials
	// where scopes were the sole obstacle, so a trail can distinguish "not
	// authorized" from "not authorized yet" without re-deriving it.
	MissingScopes []string `json:"missingScopes,omitempty"` // matching policy rule
	Outcome       Outcome  `json:"outcome"`
	Error         string   `json:"error,omitempty"`
	LatencyMs     int64    `json:"latencyMs"`

	// UpstreamCalls is how many upstream invocations this one request cost.
	// It is not always 1: a federated list fans out to every upstream, so
	// this is where a cheap-looking client request shows its real price.
	UpstreamCalls int `json:"upstreamCalls,omitempty"`
	// ItemsServed is how many tools/prompts/resources a list returned after
	// per-principal policy filtering — the size of the surface this caller
	// was actually handed, which is what lands in a model's context.
	ItemsServed int `json:"itemsServed,omitempty"`

	// ItemsCapped counts list items a policy rule's maxItems dropped —
	// distinct from items policy made invisible, because the remedies differ:
	// one is a grant the caller does not have, the other a bound the operator
	// set. The result's _meta says the same thing to the client.
	ItemsCapped int `json:"itemsCapped,omitempty"`
	// HookOutcome is what the external decision hook said: "allow", "deny",
	// or "error". Present only when a hook inspected this request. The error
	// case is the one worth alerting on: with onError "allow" it means the
	// call proceeded uninspected, which a fail-open deployment would
	// otherwise have no record of.
	HookOutcome string `json:"hookOutcome,omitempty"`

	// Usage carries counters an upstream published in its result `_meta`,
	// verbatim. fold never synthesizes these; an absent field means the
	// upstream reported nothing, not that nothing was consumed.
	Usage map[string]any `json:"usage,omitempty"`
}

Event is one audit record.

type Logger

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

Logger fans events out to sinks. A nil *Logger drops everything.

func New

func New(cfg *config.Audit, opts ...Option) *Logger

New builds a logger from config. Absent config → no audit emission.

A sink that cannot be constructed — a file path that will not open — is reported and skipped rather than failing the gateway: losing one destination should not take the endpoint down, and the error is visible at startup.

func (*Logger) Close added in v1.9.0

func (l *Logger) Close()

Close releases sinks that hold resources (open files, delivery workers). Buffered events are flushed on a bounded best-effort basis; a shutdown that waits indefinitely on an unreachable webhook is worse than a lost tail.

func (*Logger) Emit

func (l *Logger) Emit(e Event)

Emit delivers an event to every sink.

func (*Logger) StartupErrors added in v1.9.0

func (l *Logger) StartupErrors() []error

StartupErrors reports sinks that could not be constructed, so the caller can log them. Empty when every configured sink was built.

type Observer added in v1.9.0

type Observer func(sinkType, outcome string, n int)

Observer is told the fate of events, by sink type and outcome (delivered, retried, dead_lettered, dropped). The gateway turns this into metrics: audit is the single exit door, so an event that never arrives has to be countable somewhere, or a silent sink is indistinguishable from a silent system.

type Option added in v1.9.0

type Option func(*Logger)

Option configures a Logger at construction.

func WithObserver added in v1.9.0

func WithObserver(o Observer) Option

WithObserver reports delivery outcomes.

func WithPanicHook added in v1.11.0

func WithPanicHook(h PanicHook) Option

WithPanicHook routes recovered delivery-worker panics into the caller's panic accounting, so this package's recoveries alert exactly like the gateway's own (fold_panics_total, the "panic recovered" log line). Absent, a recovered panic still writes stderr — never silent, merely unaggregated.

type Outcome

type Outcome string

Outcome classifies how a request terminated.

const (
	OutcomeOK              Outcome = "ok"
	OutcomeError           Outcome = "error"
	OutcomeDenied          Outcome = "denied"
	OutcomeRateLimited     Outcome = "rate_limited"
	OutcomeUnauthenticated Outcome = "unauthenticated"
	OutcomeUpstreamDown    Outcome = "upstream_down"
	OutcomeForbidden       Outcome = "forbidden" // host/origin rejected (DNS rebinding)
	// OutcomeBudgetExhausted is distinct from OutcomeRateLimited because the
	// remedy differs: a rate limit clears in seconds, an exhausted budget not
	// until the period rolls over. Collapsing them would make "we are being
	// throttled" and "we have spent the month" the same line in a SIEM.
	OutcomeBudgetExhausted Outcome = "budget_exhausted"
	// OutcomeWarned is a finding rather than a request outcome: fold noticed
	// something an operator should see and did not change what it served.
	// Definition drift is the first of these — see
	// docs/design-definition-pinning.md. It is deliberately not an error: the
	// request succeeded, and an SLO that counted findings as failures would
	// page someone for a working federation.
	OutcomeWarned Outcome = "warned"
	// OutcomeHookDenied is the external decision hook refusing a call, kept
	// distinct from OutcomeDenied because an operator reading the trail needs
	// to know whether their policy or their inspector said no — the remedies
	// live in different systems, and often in different teams.
	OutcomeHookDenied Outcome = "hook_denied"
)

Outcomes mirror the terminal responses the gateway can produce; every audit event carries exactly one.

type PanicHook added in v1.11.0

type PanicHook func(recovered any, stack []byte)

PanicHook is told about a panic recovered inside a delivery worker: the recovered value and the stack at recovery.

type Sink

type Sink interface {
	Emit(Event)
}

Sink receives audit events.

Jump to

Keyboard shortcuts

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