Documentation
¶
Overview ¶
Package guardy provides a pipeline engine for AI guardrails: validation, intervention actions (pass, block, redact, retry), and two-phase execution (sequential Fast-Path for mutations, parallel Slow-Path via errgroup).
See .cursor/docs/task9.md for the v2 technical specification.
Index ¶
- Constants
- Variables
- func Guard[T any](p *Pipeline[T], extractor func(*http.Request) (T, error), ...) func(http.Handler) http.Handler
- func PlainTextInjector() func(*http.Request, string) error
- func WrapInput[Req, Res any](p *Pipeline[Req], next func(context.Context, Req) (Res, error)) func(context.Context, Req) (Res, error)
- func WrapOutput[Req, Res any](p *Pipeline[Res], next func(context.Context, Req) (Res, error)) func(context.Context, Req) (Res, error)
- type Action
- type GuardWriter
- type GuardWriterOption
- type Judge
- type LLMJudge
- type Matcher
- type Observer
- type Pipeline
- type PipelineOption
- type Report
- type RetryError
- type RunResult
- type SemanticValidator
- type Severity
- type ValidationPhase
- type Validator
- type ValidatorFunc
- type ValidatorMiddleware
Examples ¶
Constants ¶
const DefaultMaxBodyBytes = 1 << 20
DefaultMaxBodyBytes is the default request body size limit for Guard (1MB, DoS protection).
const DefaultStreamChunkSize = 4096
DefaultStreamChunkSize is the default buffer size for GuardWriter.
const DefaultStreamMaxChunkSize = 2048
DefaultStreamMaxChunkSize is the hard cap for delimiterless chunks.
Variables ¶
var ( // ErrBlocked is returned when the pipeline result is Block (e.g. by GuardWriter, WrapInput, WrapOutput). // Match with [errors.Is] against err and ErrBlocked. WrapInput/WrapOutput may wrap it with [fmt.Errorf]("%w: ...", ErrBlocked, reason). ErrBlocked = errors.New("guardy: input blocked") // ErrRetryRequested is returned when the pipeline result is Retry; the orchestrator should retry with Feedback. // WrapInput/WrapOutput return *RetryError, which unwraps to ErrRetryRequested — use [errors.Is] for a quick check, // or [errors.As] into *RetryError for Feedback and Report. ErrRetryRequested = errors.New("guardy: retry requested") // ErrValidatorFailed is returned when a validator returns a system error. ErrValidatorFailed = errors.New("guardy: validator failed") )
Functions ¶
func Guard ¶
func Guard[T any]( p *Pipeline[T], extractor func(*http.Request) (T, error), injector func(*http.Request, T) error, ) func(http.Handler) http.Handler
Guard returns net/http middleware that runs the pipeline on the request body. It is HTTP-specific; for generic func(context.Context, Req) (Res, error) wrapping use WrapInput / WrapOutput. Extractor reads the request and returns value of type T to validate. Injector applies mutated T to the request body on ActionRedact (required; format-aware). On Block/Retry returns 422. On Pass restores original body. Use Guard[string] with PlainTextInjector for string-based pipelines.
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"github.com/skosovsky/guardy"
"github.com/skosovsky/guardy/ext"
)
func main() {
regexV, _ := ext.NewRegexValidator(`(?i)ignore`, ext.WithCode("INJECT"))
pipeline := guardy.NewPipeline(guardy.WithFastPath(regexV))
extractor := func(r *http.Request) (string, error) {
body, _ := io.ReadAll(r.Body)
return string(body), nil
}
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
})
handler := guardy.Guard(pipeline, extractor, guardy.PlainTextInjector())(next)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("hello"))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
fmt.Println(rec.Code)
}
Output: 200
func PlainTextInjector ¶ added in v0.5.0
PlainTextInjector returns an injector that replaces the request body with the mutated string. Syncs Body, ContentLength, Header[Content-Length], and GetBody for proxy/retry compatibility.
func WrapInput ¶ added in v0.7.0
func WrapInput[Req, Res any]( p *Pipeline[Req], next func(context.Context, Req) (Res, error), ) func(context.Context, Req) (Res, error)
WrapInput runs the pipeline on the request value before calling next. Deadlines and cancellation come from ctx (e.g. wrap with context.WithTimeout at the call site); no implicit timeout is applied.
On ActionRedact, next receives the mutated Output from the pipeline. On ActionBlock, next is not called; the error wraps ErrBlocked (use errors.Is). On ActionRetry, next is not called; the error is *RetryError (use errors.As), which unwraps to ErrRetryRequested.
Composing WrapOutput(outPipeline, WrapInput(inPipeline, fn)) yields input and output guards around fn without net/http.
func WrapOutput ¶ added in v0.7.0
func WrapOutput[Req, Res any]( p *Pipeline[Res], next func(context.Context, Req) (Res, error), ) func(context.Context, Req) (Res, error)
WrapOutput runs next first, then validates the result with the pipeline. If next returns a non-nil error, the result is (res, err) with res as returned by next (possibly a zero value, e.g. nil for pointer types). Deadlines for the output pipeline use the same ctx as for next.
On ActionRedact, the returned value is the pipeline Output (mutated). On ActionBlock or ActionRetry, behavior matches WrapInput (errors and no replacement of a successful next result in the success path — the error is returned instead).
Types ¶
type Action ¶
type Action int
Action is the intervention outcome from a validator or pipeline.
type GuardWriter ¶
type GuardWriter struct {
// contains filtered or unexported fields
}
GuardWriter wraps an io.Writer and runs the pipeline on buffered chunks. On Block it returns ErrBlocked; on Redact it writes the mutated text. Uses index-based buffering to avoid O(N^2) copy; overlap prevents boundary bypass.
func NewGuardWriter ¶
func NewGuardWriter(w io.Writer, p *Pipeline[string], opts ...GuardWriterOption) *GuardWriter
NewGuardWriter creates a GuardWriter that validates data in chunks before writing.
Example ¶
package main
import (
"context"
"fmt"
"strings"
"github.com/skosovsky/guardy"
)
func main() {
v := &examplePassValidator{}
pipeline := guardy.NewPipeline(guardy.WithFastPath(v))
var out strings.Builder
gw := guardy.NewGuardWriter(&out, pipeline, guardy.WithChunkSize(64))
_, _ = gw.Write([]byte("streaming text "))
_ = gw.Close()
fmt.Println(out.String())
}
type examplePassValidator struct{}
func (examplePassValidator) Validate(_ context.Context, input string) (string, *guardy.Report, error) {
return input, &guardy.Report{Action: guardy.ActionPass}, nil
}
Output: streaming text
func (*GuardWriter) Close ¶
func (g *GuardWriter) Close() error
Close flushes the remaining buffer and validates it.
type GuardWriterOption ¶ added in v0.6.0
type GuardWriterOption func(*guardWriterConfig)
GuardWriterOption configures GuardWriter behavior.
func WithChunkSize ¶
func WithChunkSize(n int) GuardWriterOption
WithChunkSize sets the buffer size in bytes before validation runs (default 4096).
func WithContext ¶
func WithContext(ctxFn func() (context.Context, context.CancelFunc)) GuardWriterOption
WithContext sets the context factory for chunk validation.
func WithJSONAwareSplitter ¶ added in v0.6.0
func WithJSONAwareSplitter() GuardWriterOption
WithJSONAwareSplitter switches GuardWriter to JSON-aware chunk splitting.
func WithMaxChunkSize ¶ added in v0.5.5
func WithMaxChunkSize(n int) GuardWriterOption
WithMaxChunkSize sets the hard cap for delimiterless chunks (default 2048).
func WithTimeout ¶
func WithTimeout(d time.Duration) GuardWriterOption
WithTimeout sets a timeout for each chunk validation.
type LLMJudge ¶ added in v0.4.0
type LLMJudge struct {
// contains filtered or unexported fields
}
LLMJudge is a Slow-Path validator that delegates to a Judge.
func NewLLMJudge ¶ added in v0.4.0
NewLLMJudge builds a validator that calls j.Evaluate. If shadow is true and the judge returns block, the report is marked ShadowMode.
type Matcher ¶ added in v0.4.0
Matcher returns a similarity score for the text (e.g. from a vector search). Higher score means more likely to block; threshold is applied by SemanticValidator.
type Observer ¶ added in v0.4.1
Observer is a callback invoked for non-blocking shadow block reports. It receives the request context and the report; intended for telemetry.
type Pipeline ¶
type Pipeline[T any] struct { // contains filtered or unexported fields }
Pipeline orchestrates the execution of multiple Validators.
THREAD SAFETY: A Pipeline is safe for concurrent use. Configuration method Use returns a new instance and never mutates the original pipeline.
func NewPipeline ¶
func NewPipeline[T any](opts ...PipelineOption[T]) *Pipeline[T]
NewPipeline builds a pipeline from options.
Example ¶
package main
import (
"context"
"fmt"
"github.com/skosovsky/guardy"
"github.com/skosovsky/guardy/ext"
)
func main() {
regexV, _ := ext.NewRegexValidator(`(?i)(ignore previous|system prompt)`, ext.WithCode("PROMPT_INJECTION"))
lengthV := ext.NewLengthValidator(0, 10000, ext.WithCode("TOO_LONG"))
pipeline := guardy.NewPipeline(
guardy.WithFastPath(regexV, lengthV),
)
ctx := context.Background()
result, err := pipeline.Run(ctx, "Hello, what is the weather?")
if err != nil {
panic(err)
}
report := result.Decision()
if report != nil {
switch report.Action {
case guardy.ActionBlock:
// handle block
case guardy.ActionPass, guardy.ActionRedact:
_ = result.Output
_ = report.MutatedText
case guardy.ActionRetry:
_ = report.Feedback
}
}
fmt.Println("configured")
}
Output: configured
func (*Pipeline[T]) Run ¶
Run executes the pipeline. Block and Retry short-circuit immediately. Returns RunResult with Output and all Reports for telemetry.
Example ¶
package main
import (
"context"
"fmt"
"github.com/skosovsky/guardy"
"github.com/skosovsky/guardy/ext"
)
func main() {
wordlistV := ext.NewWordlistValidator([]string{"bad"}, ext.Blocklist, ext.WithCode("FORBIDDEN"))
pipeline := guardy.NewPipeline(guardy.WithFastPath(wordlistV))
ctx := context.Background()
result, err := pipeline.Run(ctx, "this is bad")
if err != nil {
panic(err)
}
report := result.Decision()
if report != nil && report.Action == guardy.ActionBlock {
fmt.Println("blocked:", report.Reason)
}
}
Output: blocked: blocklisted word found
func (*Pipeline[T]) Use ¶ added in v0.5.0
func (p *Pipeline[T]) Use(mw ...ValidatorMiddleware[T]) *Pipeline[T]
Use appends middleware and returns a new immutable pipeline instance. The original pipeline is not modified.
type PipelineOption ¶
PipelineOption configures a Pipeline.
func WithFastPath ¶ added in v0.4.0
func WithFastPath[T any](v ...Validator[T]) PipelineOption[T]
WithFastPath adds validators that run sequentially and may return redact.
func WithObserver ¶ added in v0.4.0
func WithObserver[T any](o Observer) PipelineOption[T]
WithObserver registers a callback for shadow block reports.
func WithSlowPath ¶ added in v0.4.0
func WithSlowPath[T any](v ...Validator[T]) PipelineOption[T]
WithSlowPath adds validators that run in parallel (read-only, no redact).
type Report ¶
type Report struct {
Action Action // ActionPass, ActionBlock, ActionRedact, ActionRetry
Validator string // Name of the validator that produced this report
Code string // Machine-readable rule code (for alerting/telemetry)
Severity Severity // Risk level for the report
Reason string // Human-readable reason
Feedback string // Message for LLM retry (when Action == ActionRetry)
Score float64 // Confidence or distance (optional)
ShadowMode bool // If true, block was logged but did not stop the pipeline
MutatedText string // Text after redaction (when Action == ActionRedact)
}
Report is the single result returned by a validator or the pipeline. It maps to metry/security attributes for telemetry. When Action == ActionRetry, Feedback contains the message for the LLM/orchestrator.
func ReportFromContext ¶
ReportFromContext returns the Report attached by HTTP Guard, if any.
func (*Report) CloneWithoutState ¶ added in v0.6.0
CloneWithoutState returns a copy without input-specific runtime state.
type RetryError ¶ added in v0.7.0
RetryError carries pipeline retry metadata from WrapInput or WrapOutput when Decision() is ActionRetry.
func (*RetryError) Error ¶ added in v0.7.0
func (e *RetryError) Error() string
Error implements error.
func (*RetryError) Unwrap ¶ added in v0.7.0
func (e *RetryError) Unwrap() error
Unwrap returns ErrRetryRequested so errors.Is matches *RetryError when the second argument is ErrRetryRequested.
type RunResult ¶ added in v0.5.0
type RunResult[T any] struct { Output T // Mutated output (after redactions) Reports []Report // All validator reports for telemetry }
RunResult holds the output and all reports from Pipeline.Run.
type SemanticValidator ¶ added in v0.4.0
type SemanticValidator struct {
// contains filtered or unexported fields
}
SemanticValidator is a Slow-Path validator that blocks when score exceeds threshold.
func NewSemanticValidator ¶ added in v0.4.0
func NewSemanticValidator(m Matcher, threshold float64, shadow bool) *SemanticValidator
NewSemanticValidator builds a validator that blocks when m.Match returns score > threshold. If shadow is true, block reports are marked ShadowMode so the pipeline does not short-circuit.
type Severity ¶ added in v0.6.0
type Severity string
Severity describes risk/importance of a rule hit. It is string-based for interoperability while still supporting typed constants.
type ValidationPhase ¶ added in v0.6.0
type ValidationPhase string
ValidationPhase identifies which pipeline phase is currently executing.
const ( ValidationPhaseFast ValidationPhase = "fast" ValidationPhaseSlow ValidationPhase = "slow" )
Known pipeline phases for telemetry/middleware.
func ValidationPhaseFromContext ¶ added in v0.6.0
func ValidationPhaseFromContext(ctx context.Context) (ValidationPhase, bool)
ValidationPhaseFromContext extracts the current validation phase from context.
type Validator ¶
Validator is the generic contract for validating input of type T. Returns (possibly mutated) input, a Report pointer, and an error for infrastructure failures. Report.Validator should be set by the implementation to identify the validator.
type ValidatorFunc ¶ added in v0.5.0
ValidatorFunc adapts a function to Validator[T] for use in tests and middleware.
type ValidatorMiddleware ¶ added in v0.5.0
ValidatorMiddleware wraps a Validator with cross-cutting logic (metrics, logging).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ext provides built-in validators for the guardy pipeline.
|
Package ext provides built-in validators for the guardy pipeline. |
|
jsonschema
module
|
|
|
Package guardytest provides test helpers for guardy pipelines and validators.
|
Package guardytest provides test helpers for guardy pipelines and validators. |