permclassify

package
v0.0.28 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package permclassify provides an OPTIONAL layer-2 model-based command/tool risk classifier, implemented as a pluggable decorator over a layer-1 port.PermissionPolicy. It mirrors the house decorator idiom already used by internal/adapter/llmresilience: a single Wrap function returns a value satisfying the same port the loop already consumes, so the loop, the composition root, and every other adapter are untouched.

Where it sits

Layer 1 (engine/adapter/permpolicy over engine/governance) is a fast, deterministic deny → ask → allow pre-parser with plan-mode gating and compound-Bash splitting. This package adds layer 2: for the ambiguous middle — the calls layer 1 routes to Ask — it consults a model (any port.LLMProvider) to classify the specific tool + arguments (especially Bash command strings) as safe / ambiguous / dangerous, and may sharpen the decision. The model is read on the RAW pending session.ToolCall, which naturally satisfies the "treat compaction summaries as untrusted" note from the source doc: no compacted/summarised text reaches the classifier.

Monotonicity (the safety contract)

The classifier may only move a decision in the safe direction relative to what layer 1 already decided. Concretely:

  • An inner Deny is ALWAYS returned unchanged — the model is never even consulted, and can never relax a Deny to Ask or Allow.
  • An inner Allow is returned unchanged unless ClassifyOn is set to Allow.
  • Only the ClassifyOn effect (default: Ask) is escalated to the model. From an inner Ask the model may keep Ask, tighten to Deny, or — only when it is confidently safe — relax to Allow.

In short: the decorator can make an Ask more restrictive (→ Deny) or, when confident-safe, relax it (→ Allow), but it can NEVER downgrade an inner Deny.

Fail-safe by default

This sits squarely in the security path, so the default failure posture is fail-SAFE: on a model error, a timeout, or unparseable output, the decorator falls back to the INNER decision (which, for the classified case, is Ask) so a human still gates the call. It never auto-allows on failure. Setting FailOpen true keeps that same fall-back-to-inner behaviour explicit (it does not auto-allow either) — the distinction exists so future inner effects could fail open; even then an inner Deny is never relaxed.

Concurrency

The decorator holds no mutable shared state beyond its immutable Config and the (concurrency-safe) injected collaborators, so it is safe for concurrent use.

The package depends only on the standard library and internal packages (port, governance, session, prompt). It introduces no new go.mod dependencies.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Wrap

Wrap decorates inner with a model-based layer-2 risk classifier and returns a value satisfying the same port.PermissionPolicy. llm is the model used for classification (any port.LLMProvider). The returned policy is safe for concurrent use.

Behaviour, in order:

  1. Evaluate the inner (layer-1) policy.
  2. If the inner effect is not Config.ClassifyOn (default Ask) — including any Deny or Allow — return it unchanged. The model is not consulted.
  3. Otherwise consult the model under a bounded timeout, parse a Verdict, and map it: dangerous → Deny, safe → Allow, ambiguous/unparseable → keep the inner decision. On any model error or timeout, keep the inner decision (fail-safe). An inner Deny is never reachable here and so never relaxed.

Types

type Classifier

type Classifier interface {
	// Classify returns a Verdict for c, or an error if the model could not be
	// consulted. ctx already carries the per-classification timeout.
	Classify(ctx context.Context, c session.ToolCall) (Verdict, error)
}

Classifier is the adapter-local seam that turns a tool call into a Verdict by consulting a model. classifier is the production implementation; tests may substitute their own to exercise Wrap without an LLM.

type Config

type Config struct {
	// Model is the provider model identifier used for the classification call.
	// Empty leaves LLMRequest.Model empty (the provider's default).
	Model string
	// Timeout caps a single classification. 0 selects defaultTimeout. It never
	// overrides a shorter caller deadline.
	Timeout time.Duration
	// ClassifyOn selects which inner (layer-1) effect is escalated to the model.
	// The zero value selects governance.Ask (the ambiguous middle). Inner
	// decisions whose effect differs from ClassifyOn are returned unchanged
	// without consulting the model. An inner Deny is ALWAYS returned unchanged
	// regardless of this setting (monotonicity).
	ClassifyOn governance.Effect
	// FailOpen documents the failure posture. Both values fall back to the inner
	// decision on model error/timeout/unparseable output (never auto-allow); the
	// default false is fail-safe. See the package doc.
	FailOpen bool
	// SkipReadOnly, when true, returns the inner decision unchanged for tools
	// known to be read-only (Read, Grep, Glob), skipping a model round-trip for
	// trivially safe calls. Bash is never skipped (its command string is the
	// whole point). Defaults to false to keep behaviour explicit.
	SkipReadOnly bool
}

Config tunes the classifier decorator. The zero value is usable: it defaults ClassifyOn to Ask, Timeout to defaultTimeout, and FailOpen to false (fail-safe). Supply explicit values via Wrap to override.

type Verdict

type Verdict int

Verdict is the parsed classification the model returns for a tool call.

const (
	// VerdictUnknown means the model output could not be parsed into a verdict.
	// It is treated fail-safe (the inner decision is kept).
	VerdictUnknown Verdict = iota
	// VerdictSafe means the model judged the call clearly safe.
	VerdictSafe
	// VerdictAmbiguous means the model could not confidently judge the call; the
	// inner Ask is kept so a human decides.
	VerdictAmbiguous
	// VerdictDangerous means the model judged the call dangerous; it is escalated
	// to Deny.
	VerdictDangerous
)

Jump to

Keyboard shortcuts

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