rules

package
v0.700.0 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package rules is PromptZero's reactive rules engine. It subscribes to the audit observer and fans matching entries out to declarative actions: webhook calls, slog lines — and, optionally, agent tool invocations when the engine is wired with a runner.

Design

Each Rule has a Match predicate (AND over non-empty fields) and a list of Actions. An Engine owns a registry of rules plus per-rule state (enabled flag, last-fire time for cooldown accounting). Engine.Handle is the audit observer: it runs every rule's Match, applies cooldown, renders the template, and dispatches the actions. Dispatch is synchronous from the observer but non-blocking because the underlying webhook layer already queues internally.

The match DSL is deliberately thin — reactive rules are cheap sugar on top of the audit stream, not a pipeline runtime. Complex transformations belong in a workflow or an external subscriber.

Index

Constants

View Source
const (
	VerdictSuccess    = "success"
	VerdictFailure    = "failure"
	VerdictSuspicious = "suspicious"
	VerdictUnknown    = "unknown"
)

Known verdict values. Callers should prefer these constants over string literals so a typo becomes a compile error.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action

type Action struct {
	Kind    ActionKind
	Webhook string // logical webhook name passed to WebhookFire
	Tool    string // tool name for ActionTool
	Params  map[string]interface{}
}

Action is one step. Fields not relevant to the kind are left zero.

type ActionKind

type ActionKind string

ActionKind enumerates the built-in action types. "webhook" fires via the injected WebhookFire hook, "log" emits a slog line, "tool" (optional) runs an agent tool via RunTool.

const (
	ActionWebhook ActionKind = "webhook"
	ActionLog     ActionKind = "log"
	ActionTool    ActionKind = "tool"
)

type Deps

type Deps struct {
	WebhookFire func(name string, payload map[string]any)
	RunTool     func(ctx context.Context, tool string, params map[string]interface{}) (string, error)
	Now         func() time.Time
}

Deps are the host-supplied side-effect handlers. Any of them may be nil; actions requiring a nil hook are skipped with a slog warning at fire time. This keeps the engine usable in tests without webhook plumbing.

type Detector added in v0.3.0

type Detector interface {
	Name() string
	Evaluate(ctx context.Context, tool, input, output string) (Verdict, error)
}

Detector evaluates a tool invocation and emits a Verdict. The input is the tool's JSON arguments, the output is whatever the tool returned (possibly wrapped in ToolError JSON on failure). The name is the tool name for routing context-specific prompts.

Implementations must honour ctx cancellation — detectors are often LLM-backed and can otherwise wedge the caller for tens of seconds on a slow classifier model.

type DetectorEngine added in v0.3.0

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

DetectorEngine is a per-tool detector registry with a concurrent evaluator. The agent calls EvaluateFor after a tool invocation and receives every registered detector's Verdict. Multiple detectors can register for the same tool — useful when a single tool has orthogonal success criteria (e.g. wifi_deauth both "disconnected the station" and "didn't trip the vendor's rate-limit").

Safe for concurrent registration + evaluation; zero value is NOT usable — call NewDetectorEngine.

func NewDetectorEngine added in v0.3.0

func NewDetectorEngine(timeout time.Duration) *DetectorEngine

NewDetectorEngine returns an Engine with the given per-detector timeout. Timeout caps how long a single detector call can block EvaluateFor — detectors are LLM-backed and a stalled classifier API would otherwise wedge the agent turn. Ten seconds matches the verifier's default (see internal/agent/verify.go).

func (*DetectorEngine) EvaluateFor added in v0.3.0

func (e *DetectorEngine) EvaluateFor(ctx context.Context, toolName, input, output string) []Verdict

EvaluateFor runs every detector registered for toolName and returns their Verdicts. Detectors run concurrently under the engine's timeout — any detector that errors or times out contributes a VerdictUnknown rather than taking down the whole evaluation. Returns an empty slice when no detectors are registered for the tool.

func (*DetectorEngine) HasDetectorsFor added in v0.3.0

func (e *DetectorEngine) HasDetectorsFor(toolName string) bool

HasDetectorsFor reports whether the engine has at least one detector registered for toolName. Callers use this to skip the EvaluateFor round-trip entirely when no detector is listening.

func (*DetectorEngine) Register added in v0.3.0

func (e *DetectorEngine) Register(toolName string, d Detector)

Register installs a detector to run after any invocation of the named tool. A single detector can be registered against multiple tools (e.g. the deauth-success judge matches both wifi_deauth and wifi_deauth_station_list).

func (*DetectorEngine) RegisterBuiltins added in v0.3.0

func (e *DetectorEngine) RegisterBuiltins(judge JudgeFunc) *DetectorEngine

RegisterBuiltins installs the three built-in detectors against the standard tool surfaces they judge. All share a single JudgeFunc — typically a thin wrapper over the agent's classification-tier Anthropic client. Returns the engine so callers can chain.

func (*DetectorEngine) RegisterForMany added in v0.3.0

func (e *DetectorEngine) RegisterForMany(tools []string, d Detector)

RegisterForMany is a convenience wrapper that registers d against every name in tools. Useful for built-in detectors that should fire on a family of related tools.

type Engine

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

Engine holds the rule registry, per-rule cooldown state, and deps. All methods are goroutine-safe. Zero value is NOT usable — call New.

func New

func New(deps Deps) *Engine

New builds an Engine with no rules installed.

func (*Engine) Handle

func (e *Engine) Handle(entry audit.Entry)

Handle is the audit observer. It evaluates every rule against the entry, honours cooldown, and dispatches matching rules' actions. Dispatch errors are logged but never surface to the caller — the audit observer path must not block on them.

func (*Engine) List

func (e *Engine) List() []Snapshot

List returns the rule registry as a slice of Snapshots, sorted by name.

func (*Engine) Pause

func (e *Engine) Pause(name string) bool

Pause disables the named rule (without removing it).

func (*Engine) Register

func (e *Engine) Register(r Rule)

Register adds or replaces a rule. Re-registering a name resets its cooldown and fire counter.

The Rule docstring promises "Enabled defaults true when the rule is registered; flip it via Pause." — Register enforces that here by setting cp.Enabled = true unconditionally before storing. Pre-this-fix Register passed the bool through verbatim, so a caller writing `eng.Register(Rule{Name: ..., Match: ..., Actions: ...})` (without explicitly setting Enabled: true) would silently get a never-firing rule — Go's zero value for bool is false, and Handle's `if !r.Enabled { continue }` skipped them. Operators who genuinely want a registered-but-paused rule call Pause(name) after Register; that path remains unchanged.

func (*Engine) Remove

func (e *Engine) Remove(name string)

Remove drops a rule by name. No-op if absent.

func (*Engine) Resume

func (e *Engine) Resume(name string) bool

Resume re-enables the named rule and clears its cooldown so the next matching entry fires immediately.

func (*Engine) Test

func (e *Engine) Test(name string, entry audit.Entry) ([]string, error)

Test renders the actions without side effects and returns the substitution output so operators can preview a rule. Used by `/rules test`.

type JudgeFunc added in v0.3.0

type JudgeFunc func(ctx context.Context, system, user string) (string, error)

JudgeFunc is the LLM-as-judge callback used by LLMDetector. Takes a system prompt + a user message and returns the judge's raw text output. Injected here so unit tests can substitute a deterministic stub without wiring up the Anthropic SDK. In production wiring this is typically a thin wrapper over the agent's TierClassify model (Haiku).

type LLMDetector added in v0.3.0

type LLMDetector struct {
	DetectorName string
	SystemPrompt string
	Judge        JudgeFunc
}

LLMDetector turns a system prompt + a JudgeFunc into a Detector. The judge is asked to return JSON matching the Verdict shape; any parsing failure (prose instead of JSON, missing verdict field) surfaces as a VerdictUnknown so downstream code can decide whether to retry or escalate.

func NewDeauthSuccessDetector added in v0.3.0

func NewDeauthSuccessDetector(judge JudgeFunc) *LLMDetector

NewDeauthSuccessDetector returns the built-in deauth judge.

func NewNFCCloneFidelityDetector added in v0.3.0

func NewNFCCloneFidelityDetector(judge JudgeFunc) *LLMDetector

NewNFCCloneFidelityDetector returns the built-in NFC-clone judge.

func NewPMKIDValidityDetector added in v0.3.0

func NewPMKIDValidityDetector(judge JudgeFunc) *LLMDetector

NewPMKIDValidityDetector returns the built-in PMKID judge.

func (*LLMDetector) Evaluate added in v0.3.0

func (d *LLMDetector) Evaluate(ctx context.Context, tool, input, output string) (Verdict, error)

Evaluate runs the judge and parses its response into a Verdict. Any error path produces a structured VerdictUnknown rather than a Go error — a broken detector must never derail the caller (detector outputs are advisory).

func (*LLMDetector) Name added in v0.3.0

func (d *LLMDetector) Name() string

Name returns the detector's registration name.

type Match

type Match struct {
	Tool           string
	Risk           string
	Level          string
	OutputContains string
	// Success filters by audit Entry.Success. nil matches either; &true
	// fires only on successful tool calls; &false fires only on
	// failures. Mirrors audit.Filter.Success — same nil/true/false
	// tristate so an operator can alert on every failed wifi_deauth or
	// chain a follow-up only when wifi_handshake_capture succeeds.
	Success *bool
}

Match is the AND-over-non-empty predicate applied to an audit.Entry. Tool supports a trailing "*" glob so "workflow_*" matches any workflow_<name> entry.

type Rule

type Rule struct {
	Name        string
	Description string
	Match       Match
	Actions     []Action
	Cooldown    time.Duration
	Enabled     bool
}

Rule is one registered match -> actions binding. Name is unique. Cooldown suppresses re-fires within the window; use 0 for no cooldown. Enabled defaults true when the rule is registered; flip it via Pause.

type Snapshot

type Snapshot struct {
	Name        string
	Description string
	Enabled     bool
	Fires       int
	LastFire    time.Time
	// Cooldown mirrors Rule.Cooldown so consumers (web API, /rules
	// list) can render the configured suppression window without
	// reaching into the registry. Zero means no cooldown.
	Cooldown time.Duration
}

Snapshot is a read-only view of one rule's state.

type Verdict added in v0.3.0

type Verdict struct {
	Verdict    string  `json:"verdict"`               // "success" | "failure" | "suspicious" | "unknown"
	Confidence float64 `json:"confidence"`            // 0.0-1.0
	Evidence   string  `json:"evidence,omitempty"`    // one or two sentences citing the output
	DetectedBy string  `json:"detected_by,omitempty"` // detector name, set by Engine
}

Verdict is the structured output of a Detector. Grep-friendly on wire: downstream consumers (reports, audit analytics, the future Campaigns runner) pattern-match on Verdict alone rather than parsing free-form Evidence prose.

The three-value scheme maps to Garak / PyRIT's probe detector taxonomy: a tool invocation has a clean success, a clean failure, or a deceptive / ambiguous response that merits follow-up ("suspicious"). The suspicious bucket is the important addition — a deauth tool that reports success despite the target ignoring it produces a suspicious verdict, not a success.

func (Verdict) JSON added in v0.3.0

func (v Verdict) JSON() string

JSON serialises the verdict to a compact wire representation. Used for audit-log attachment and /report rendering.

Jump to

Keyboard shortcuts

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