action

package
v1.0.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Feb 18, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package action defines the CanonicalAction type system: a protocol-agnostic representation of any agent action flowing through SentinelGate. Every action — regardless of protocol (MCP, HTTP, WebSocket, runtime) — is normalized into a CanonicalAction for uniform policy evaluation.

Index

Constants

View Source
const (
	// DefaultApprovalTimeout is the default timeout for pending approvals.
	DefaultApprovalTimeout = 5 * time.Minute
	// DefaultMaxPending is the default maximum number of pending approvals.
	DefaultMaxPending = 100
)

Variables

View Source
var ErrOutboundBlocked = fmt.Errorf("outbound blocked")

ErrOutboundBlocked is the sentinel error for outbound-blocked actions.

View Source
var ErrOutboundRuleNotFound = errors.New("outbound rule not found")

ErrOutboundRuleNotFound is returned when a requested outbound rule does not exist.

View Source
var ErrResponseBlocked = errors.New("response blocked by content scanning")

ErrResponseBlocked is returned when response content scanning detects prompt injection in enforce mode.

Functions

func MatchRule

func MatchRule(rule OutboundRule, domain string, ip string, port int) bool

MatchRule returns true if ANY target in the rule matches the given destination.

func MatchTarget

func MatchTarget(target OutboundTarget, domain string, ip string, port int) bool

MatchTarget evaluates whether a single target matches the given destination properties (domain, ip, port).

Types

type ActionIdentity

type ActionIdentity struct {
	// ID is the unique identifier for the actor.
	ID string
	// Name is the display name of the actor.
	Name string
	// Roles are the roles assigned to the actor.
	Roles []string
	// SessionID is the session identifier for the actor.
	SessionID string
}

ActionIdentity represents the WHO of an action: the actor performing it.

type ActionInterceptor

type ActionInterceptor interface {
	// Intercept processes a CanonicalAction and returns the result.
	// Returns the (possibly modified) action and an error if rejected.
	Intercept(ctx context.Context, action *CanonicalAction) (*CanonicalAction, error)
}

ActionInterceptor processes CanonicalActions through the security chain. This is the protocol-agnostic replacement for proxy.MessageInterceptor. During migration, LegacyAdapter wraps existing MessageInterceptors.

type ActionInterceptorFunc

type ActionInterceptorFunc func(ctx context.Context, action *CanonicalAction) (*CanonicalAction, error)

ActionInterceptorFunc is an adapter to allow the use of ordinary functions as ActionInterceptors. Like http.HandlerFunc, it enables inline interceptors.

func (ActionInterceptorFunc) Intercept

Intercept calls f(ctx, action).

type ActionType

type ActionType string

ActionType categorizes the kind of action being performed.

const (
	// ActionToolCall represents an MCP tools/call or equivalent tool invocation.
	ActionToolCall ActionType = "tool_call"
	// ActionHTTPRequest represents an outbound HTTP request from an agent.
	ActionHTTPRequest ActionType = "http_request"
	// ActionWebSocketMessage represents a WebSocket message.
	ActionWebSocketMessage ActionType = "websocket_message"
	// ActionCommandExec represents a command execution (shell, subprocess).
	ActionCommandExec ActionType = "command_exec"
	// ActionFileAccess represents a file read/write/delete operation.
	ActionFileAccess ActionType = "file_access"
	// ActionNetworkConnect represents a raw network connection attempt.
	ActionNetworkConnect ActionType = "network_connect"
	// ActionSampling represents an MCP sampling/createMessage request.
	ActionSampling ActionType = "sampling"
	// ActionElicitation represents an MCP elicitation/create request.
	ActionElicitation ActionType = "elicitation"
)

func (ActionType) String

func (t ActionType) String() string

String returns the string representation of the ActionType.

type ApprovalInterceptor

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

ApprovalInterceptor blocks tool calls that require human approval. It reads the policy Decision from context (set by PolicyActionInterceptor). If RequiresApproval is true, it creates a PendingApproval entry and blocks until the request is approved, denied, or times out.

func NewApprovalInterceptor

func NewApprovalInterceptor(store *ApprovalStore, next ActionInterceptor, logger *slog.Logger) *ApprovalInterceptor

NewApprovalInterceptor creates a new ApprovalInterceptor.

func (*ApprovalInterceptor) Intercept

Intercept checks if the tool call requires approval. If so, it blocks until the request is approved, denied, or times out. Otherwise, it passes through.

type ApprovalResult

type ApprovalResult struct {
	Approved bool
	Reason   string
}

ApprovalResult carries the outcome of an approval decision.

type ApprovalStore

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

ApprovalStore manages pending approval requests with bounded capacity. It is thread-safe and supports FIFO eviction when capacity is reached.

func NewApprovalStore

func NewApprovalStore(maxSize int) *ApprovalStore

NewApprovalStore creates a new ApprovalStore with the given maximum capacity.

func (*ApprovalStore) Add

func (s *ApprovalStore) Add(approval *PendingApproval) string

Add stores a new pending approval and returns its ID. If the store is at capacity, the oldest pending approval is evicted (auto-denied).

func (*ApprovalStore) Approve

func (s *ApprovalStore) Approve(id string) error

Approve sends an approval result to the blocked goroutine and removes the entry.

func (*ApprovalStore) Deny

func (s *ApprovalStore) Deny(id, reason string) error

Deny sends a denial result to the blocked goroutine and removes the entry.

func (*ApprovalStore) Get

func (s *ApprovalStore) Get(id string) *PendingApproval

Get returns a pending approval by ID, or nil if not found.

func (*ApprovalStore) List

func (s *ApprovalStore) List() []*PendingApproval

List returns all pending approvals (status == "pending").

type CanonicalAction

type CanonicalAction struct {

	// Identity identifies the actor performing the action.
	Identity ActionIdentity

	// Type categorizes the action (tool_call, http_request, etc.).
	Type ActionType
	// Name is the action name (tool name, HTTP method, command name, etc.).
	Name string
	// Arguments contains the action parameters (tool args, query params, etc.).
	Arguments map[string]interface{}

	// Destination captures the target of the action.
	Destination Destination

	// Protocol is the originating protocol (mcp, http, websocket, runtime).
	Protocol string
	// Framework is the agent framework (crewai, autogen, langchain, etc.).
	Framework string
	// Gateway is the gateway that received the action (mcp-gateway, http-gateway, runtime).
	Gateway string

	// RequestTime is when the action was received.
	RequestTime time.Time
	// RequestID uniquely identifies this action request.
	RequestID string
	// Metadata is an extensible bag for protocol-specific data.
	Metadata map[string]interface{}

	// OriginalMessage stores the original protocol-specific message
	// (e.g., *mcp.Message, *http.Request) for denormalization.
	OriginalMessage interface{}
}

CanonicalAction is the universal representation of any agent action. It uses a WHO/WHAT/WHERE/HOW/CONTEXT model to capture all relevant information for policy evaluation, regardless of the originating protocol.

type DNSResolver

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

DNSResolver provides DNS resolution with per-request pinning and TTL-based caching. It prevents DNS rebinding attacks by pinning the first resolution result for the lifetime of a request.

func NewDNSResolver

func NewDNSResolver(logger *slog.Logger, opts ...DNSResolverOption) *DNSResolver

NewDNSResolver creates a new DNS resolver with optional configuration.

func (*DNSResolver) CleanExpired

func (r *DNSResolver) CleanExpired()

CleanExpired removes expired cache entries.

func (*DNSResolver) PinForRequest

func (r *DNSResolver) PinForRequest(requestID string, domain string, resolved *ResolvedDest)

PinForRequest stores a resolution result pinned to a specific request.

func (*DNSResolver) ReleaseRequest

func (r *DNSResolver) ReleaseRequest(requestID string)

ReleaseRequest removes all pinned resolutions for a completed request. Should be called when request processing is done to free memory.

func (*DNSResolver) Resolve

func (r *DNSResolver) Resolve(ctx context.Context, domain string, requestID string) (*ResolvedDest, error)

Resolve resolves a domain to IPs with per-request pinning. For a given requestID+domain combination, it always returns the same PinnedIP, even if the underlying DNS record changes (rebinding protection).

type DNSResolverOption

type DNSResolverOption func(*DNSResolver)

DNSResolverOption configures a DNSResolver.

func WithDefaultTTL

func WithDefaultTTL(ttl time.Duration) DNSResolverOption

WithDefaultTTL sets the default cache TTL for resolved entries.

func WithLookupFunc

func WithLookupFunc(fn func(host string) ([]string, error)) DNSResolverOption

WithLookupFunc sets a custom DNS lookup function (useful for testing).

type Decision

type Decision string

Decision represents the outcome of policy evaluation for an action.

const (
	// DecisionAllow permits the action to proceed.
	DecisionAllow Decision = "allow"
	// DecisionDeny blocks the action.
	DecisionDeny Decision = "deny"
	// DecisionApprovalRequired blocks the action pending human approval.
	DecisionApprovalRequired Decision = "approval_required"
)

func (Decision) String

func (d Decision) String() string

String returns the string representation of the Decision.

type Destination

type Destination struct {
	// URL is the full URL if available.
	URL string
	// Domain is the domain name (e.g., "api.example.com").
	Domain string
	// IP is the resolved IP address.
	IP string
	// Port is the port number (0 = unset).
	Port int
	// Scheme is the protocol scheme (http, https, ws, wss, etc.).
	Scheme string
	// Path is the URL path or file path.
	Path string
	// Command is the command name for command_exec actions.
	Command string
	// CmdArgs are the command arguments for command_exec actions.
	CmdArgs []string
}

Destination captures where an action is directed: URL, domain, IP, port, scheme, path, command, and command arguments.

type ExtractOptions

type ExtractOptions struct {
	// Base64Decode enables base64 decoding of string values before extraction.
	Base64Decode bool
	// MaxDepth is the maximum recursion depth (default 10 when 0).
	MaxDepth int
}

ExtractOptions configures URL extraction behavior.

type ExtractedURL

type ExtractedURL struct {
	// RawValue is the original string value found.
	RawValue string
	// URL is the parsed full URL (or constructed from IP:port).
	URL string
	// Domain is the domain name (empty if IP address).
	Domain string
	// IP is the IP address if directly specified (empty if domain).
	IP string
	// Port is the port number (0 if not specified, 80/443 inferred from scheme).
	Port int
	// Scheme is the protocol scheme (http, https, ws, wss, etc.).
	Scheme string
	// Path is the URL path.
	Path string
	// Source is the argument key path where found (e.g., "url", "config.endpoint").
	Source string
}

ExtractedURL represents a URL or IP:port pattern found in CanonicalAction arguments.

func ExtractURLs

func ExtractURLs(args map[string]interface{}, opts ExtractOptions) []ExtractedURL

ExtractURLs scans a map[string]interface{} (typically CanonicalAction.Arguments) for URLs, IP:port patterns, and URLs embedded in text. It returns all discovered ExtractedURL entries, deduplicated by URL.

type HTTPNormalizer

type HTTPNormalizer struct{}

HTTPNormalizer converts *http.Request to/from CanonicalAction. It maps HTTP requests into the universal CanonicalAction representation so the entire security chain evaluates HTTP requests identically to MCP requests.

func NewHTTPNormalizer

func NewHTTPNormalizer() *HTTPNormalizer

NewHTTPNormalizer creates a new HTTPNormalizer.

func (*HTTPNormalizer) Denormalize

func (n *HTTPNormalizer) Denormalize(action *CanonicalAction, result *InterceptResult) (interface{}, error)

Denormalize converts an InterceptResult back to HTTP response info. For allow decisions, returns the original *http.Request (the proxy handler will forward it). For deny decisions, returns nil and an error with the deny reason.

func (*HTTPNormalizer) Normalize

func (n *HTTPNormalizer) Normalize(ctx context.Context, msg interface{}) (*CanonicalAction, error)

Normalize converts an *http.Request to a CanonicalAction. The msg parameter must be an *http.Request; other types return an error.

func (*HTTPNormalizer) Protocol

func (n *HTTPNormalizer) Protocol() string

Protocol returns "http" indicating this normalizer handles HTTP protocol requests.

type InterceptResult

type InterceptResult struct {
	// Decision is the evaluation outcome (allow, deny, approval_required).
	Decision Decision
	// Reason explains why the decision was made.
	Reason string
	// RuleID is the identifier of the rule that produced this result.
	RuleID string
	// RuleName is the human-readable name of the matching rule.
	RuleName string
	// HelpURL points to documentation about why the action was blocked.
	HelpURL string
	// HelpText provides inline guidance about the decision.
	HelpText string
	// Modifications contains optional modifications to the action
	// (e.g., argument rewriting, header injection).
	Modifications map[string]interface{}
	// ApprovalTimeout is how long to wait for human approval.
	ApprovalTimeout time.Duration
	// ApprovalTimeoutAction is the fallback decision when approval times out.
	ApprovalTimeoutAction Decision
}

InterceptResult carries the result of intercepting and evaluating an action.

func (*InterceptResult) IsAllowed

func (r *InterceptResult) IsAllowed() bool

IsAllowed returns true if the decision permits the action to proceed.

type InterceptorChain

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

InterceptorChain wraps the legacy MessageInterceptor chain with normalize/denormalize. It implements proxy.MessageInterceptor so it can be used as a drop-in replacement in ProxyService without modifying the proxy service code.

Flow: mcp.Message -> MCPNormalizer.Normalize -> ActionInterceptors -> extract mcp.Message from CanonicalAction.OriginalMessage

func NewInterceptorChain

func NewInterceptorChain(normalizer Normalizer, head ActionInterceptor, logger *slog.Logger) *InterceptorChain

NewInterceptorChain creates an InterceptorChain that normalizes incoming mcp.Messages into CanonicalActions, runs them through the ActionInterceptor chain, and extracts the resulting mcp.Message.

func (*InterceptorChain) Intercept

func (c *InterceptorChain) Intercept(ctx context.Context, msg *mcp.Message) (*mcp.Message, error)

Intercept implements proxy.MessageInterceptor. It normalizes the mcp.Message into a CanonicalAction, runs the ActionInterceptor chain, and extracts the resulting mcp.Message from the CanonicalAction.

type LegacyAdapter

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

LegacyAdapter wraps an existing proxy.MessageInterceptor to work with the new ActionInterceptor interface. During migration, each existing interceptor is wrapped in a LegacyAdapter. The adapter: 1. Extracts the original mcp.Message from CanonicalAction.OriginalMessage 2. Calls the legacy interceptor's Intercept(ctx, msg) 3. If the legacy interceptor modified the message, updates CanonicalAction accordingly 4. Returns the CanonicalAction

func NewLegacyAdapter

func NewLegacyAdapter(legacy proxy.MessageInterceptor, name string) *LegacyAdapter

NewLegacyAdapter creates a new LegacyAdapter wrapping the given MessageInterceptor.

func (*LegacyAdapter) Intercept

func (a *LegacyAdapter) Intercept(ctx context.Context, action *CanonicalAction) (*CanonicalAction, error)

Intercept extracts the mcp.Message from the CanonicalAction, calls the legacy interceptor, and syncs any changes back to the CanonicalAction.

func (*LegacyAdapter) Name

func (a *LegacyAdapter) Name() string

Name returns the adapter's name for logging/debugging.

type MCPNormalizer

type MCPNormalizer struct{}

MCPNormalizer converts mcp.Message to/from CanonicalAction. It handles tools/call, sampling/createMessage, and elicitation/create methods, mapping each to the appropriate ActionType.

func NewMCPNormalizer

func NewMCPNormalizer() *MCPNormalizer

NewMCPNormalizer creates a new MCPNormalizer.

func (*MCPNormalizer) Denormalize

func (n *MCPNormalizer) Denormalize(action *CanonicalAction, result *InterceptResult) (interface{}, error)

Denormalize converts an InterceptResult back to a protocol-specific response. For allow decisions, returns the original mcp.Message unchanged. For deny or approval_required decisions, returns nil and an error.

func (*MCPNormalizer) Normalize

func (n *MCPNormalizer) Normalize(ctx context.Context, msg interface{}) (*CanonicalAction, error)

Normalize converts an mcp.Message to a CanonicalAction. The msg parameter must be a *mcp.Message; other types return an error. Non-request messages (responses) are passed through with minimal fields.

func (*MCPNormalizer) Protocol

func (n *MCPNormalizer) Protocol() string

Protocol returns "mcp" indicating this normalizer handles MCP protocol messages.

type MemoryOutboundStore

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

MemoryOutboundStore implements OutboundRuleStore with thread-safe in-memory storage.

func NewMemoryOutboundStore

func NewMemoryOutboundStore() *MemoryOutboundStore

NewMemoryOutboundStore creates a new empty MemoryOutboundStore.

func (*MemoryOutboundStore) Delete

func (s *MemoryOutboundStore) Delete(_ context.Context, id string) error

Delete removes a rule by ID. Returns ErrOutboundRuleNotFound if not found.

func (*MemoryOutboundStore) Get

Get returns a copy of the rule with the given ID. Returns ErrOutboundRuleNotFound if the rule does not exist.

func (*MemoryOutboundStore) List

List returns all rules as copies, sorted by Priority ascending. Both enabled and disabled rules are returned.

func (*MemoryOutboundStore) Save

Save creates or updates a rule. Stores a deep copy so external modifications do not affect stored data.

type Normalizer

type Normalizer interface {
	// Normalize converts a protocol-specific message to a CanonicalAction.
	// The original message is stored in CanonicalAction.OriginalMessage
	// for denormalization on the response path.
	Normalize(ctx context.Context, msg interface{}) (*CanonicalAction, error)

	// Denormalize converts an InterceptResult back to a protocol-specific response.
	// Uses CanonicalAction.OriginalMessage to construct the response.
	// For allow decisions, returns the original message.
	// For deny decisions, returns nil and an error describing the denial.
	Denormalize(action *CanonicalAction, result *InterceptResult) (interface{}, error)

	// Protocol returns the protocol name this normalizer handles (e.g., "mcp").
	Protocol() string
}

Normalizer converts between protocol-specific messages and CanonicalAction. Each protocol (MCP, HTTP, WebSocket, runtime) has its own Normalizer implementation that knows how to extract WHO/WHAT/WHERE/HOW/CONTEXT fields from its native message format.

type OutboundDenyError

type OutboundDenyError struct {
	Domain   string
	IP       string
	Port     int
	RuleName string
	HelpText string
	HelpURL  string
	Reason   string
}

OutboundDenyError provides structured deny information for outbound blocks.

func (*OutboundDenyError) Error

func (e *OutboundDenyError) Error() string

Error implements the error interface.

func (*OutboundDenyError) Unwrap

func (e *OutboundDenyError) Unwrap() error

Unwrap returns the sentinel error for errors.Is() support.

type OutboundInterceptor

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

OutboundInterceptor extracts URLs from action arguments, resolves DNS, evaluates against outbound rules, and blocks denied destinations. It populates action.Destination fields so that downstream CEL policy evaluation can use dest_* variables.

Rules are stored via an atomic pointer for lock-free reads during the hot path. The SetRules method enables dynamic rule replacement without locking the interceptor.

func NewOutboundInterceptor

func NewOutboundInterceptor(rules []OutboundRule, resolver *DNSResolver, next ActionInterceptor, logger *slog.Logger) *OutboundInterceptor

NewOutboundInterceptor creates a new OutboundInterceptor. The initial rules are stored via atomic pointer. nil is treated as empty.

func (*OutboundInterceptor) Intercept

Intercept processes a CanonicalAction by extracting URLs, resolving DNS, evaluating outbound rules, and populating Destination fields.

func (*OutboundInterceptor) SetRules

func (o *OutboundInterceptor) SetRules(rules []OutboundRule)

SetRules atomically replaces the interceptor's rules with the provided set. Rules are sorted by Priority ascending before storage. This enables the admin service to reload rules without locking the interceptor's hot path.

type OutboundRule

type OutboundRule struct {
	// ID uniquely identifies this rule.
	ID string
	// Name is the human-readable rule name.
	Name string
	// Mode is blocklist or allowlist.
	Mode RuleMode
	// Targets are the target specifications for this rule.
	Targets []OutboundTarget
	// Action is what happens when the rule matches (block, alert, log).
	Action RuleAction
	// Scope is empty for global rules, otherwise a scope identifier (Phase 4).
	Scope string
	// Priority determines evaluation order (lower = higher priority).
	Priority int
	// Enabled controls whether this rule is active.
	Enabled bool
	// Base64Scan enables base64 URL decoding in URL extraction (OUT-04).
	Base64Scan bool
	// HelpText is shown in deny messages (OUT-10).
	HelpText string
	// HelpURL is a link shown in deny messages (OUT-10).
	HelpURL string
	// ReadOnly is true for default blocklist rules that cannot be modified or deleted.
	ReadOnly bool
	// CreatedAt is when this rule was created.
	CreatedAt time.Time
	// UpdatedAt is when this rule was last modified.
	UpdatedAt time.Time
}

OutboundRule defines a rule for controlling outbound network access.

func DefaultBlocklistRules

func DefaultBlocklistRules() []OutboundRule

DefaultBlocklistRules returns the default outbound blocklist rules active on a fresh SentinelGate installation. These rules block common data exfiltration channels and access to private/internal networks.

func EvaluateDestination

func EvaluateDestination(rules []OutboundRule, domain, ip string, port int, logger *slog.Logger) (bool, *OutboundRule)

evaluateDestination checks a destination against the given outbound rules, grouping by scope and evaluating by mode. Returns (true, rule) if blocked, (false, nil) if allowed. For blocklist rules with action "alert" or "log", the match is logged but the destination is not blocked.

type OutboundRuleStore

type OutboundRuleStore interface {
	// List returns all rules sorted by Priority ascending.
	List(ctx context.Context) ([]OutboundRule, error)
	// Get returns a single rule by ID. Returns ErrOutboundRuleNotFound if not found.
	Get(ctx context.Context, id string) (*OutboundRule, error)
	// Save creates or updates a rule in the store.
	Save(ctx context.Context, rule *OutboundRule) error
	// Delete removes a rule by ID. Returns ErrOutboundRuleNotFound if not found.
	Delete(ctx context.Context, id string) error
}

OutboundRuleStore defines CRUD operations for outbound rules.

type OutboundTarget

type OutboundTarget struct {
	// Type is the kind of target (domain, ip, cidr, domain_glob, port_range).
	Type TargetType
	// Value is the target value (e.g., "evil.com", "10.0.0.0/8", "*.ngrok.io", "1-1024").
	Value string
}

OutboundTarget represents a single target specification in an outbound rule.

type PendingApproval

type PendingApproval struct {
	ID            string                 `json:"id"`
	ToolName      string                 `json:"tool_name"`
	Arguments     map[string]interface{} `json:"arguments,omitempty"`
	IdentityName  string                 `json:"identity_name"`
	IdentityID    string                 `json:"identity_id"`
	Status        string                 `json:"status"` // "pending", "approved", "denied", "timed_out"
	CreatedAt     time.Time              `json:"created_at"`
	Timeout       time.Duration          `json:"-"`
	TimeoutAction policy.Action          `json:"-"`
	// contains filtered or unexported fields
}

PendingApproval represents a tool call that is blocked pending human approval.

type PolicyActionInterceptor

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

PolicyActionInterceptor evaluates CanonicalActions against RBAC policies. This is the natively migrated version of proxy.PolicyInterceptor -- it operates directly on CanonicalAction instead of going through LegacyAdapter. It proves the CANON-10 migration path: each interceptor can be individually rewritten to use CanonicalAction fields directly.

func NewPolicyActionInterceptor

func NewPolicyActionInterceptor(engine policy.PolicyEngine, next ActionInterceptor, logger *slog.Logger) *PolicyActionInterceptor

NewPolicyActionInterceptor creates a new PolicyActionInterceptor.

func (*PolicyActionInterceptor) Intercept

Intercept evaluates tool calls and HTTP requests against policies before passing to the next interceptor. Other action types pass through without policy evaluation.

type QuarantineChecker

type QuarantineChecker interface {
	IsQuarantined(toolName string) bool
}

QuarantineChecker is satisfied by ToolSecurityService.

type QuarantineInterceptor

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

QuarantineInterceptor blocks calls to quarantined tools. It sits before the PolicyActionInterceptor in the chain so that quarantined tools are immediately rejected regardless of policy.

func NewQuarantineInterceptor

func NewQuarantineInterceptor(checker QuarantineChecker, next ActionInterceptor, logger *slog.Logger) *QuarantineInterceptor

NewQuarantineInterceptor creates a QuarantineInterceptor.

func (*QuarantineInterceptor) Intercept

Intercept blocks quarantined tool calls, passes everything else through.

type ResolvedDest

type ResolvedDest struct {
	// Domain is the original domain that was resolved.
	Domain string
	// IPs contains all resolved IP addresses.
	IPs []string
	// PinnedIP is the first resolved IP, pinned for the request lifetime
	// to prevent DNS rebinding attacks.
	PinnedIP string
	// CachedAt is when this resolution was cached.
	CachedAt time.Time
	// TTL is how long this cache entry is valid.
	TTL time.Duration
}

ResolvedDest holds the result of a DNS resolution, including all resolved IPs and the pinned IP for a specific request.

type ResponseScanInterceptor

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

ResponseScanInterceptor scans MCP tool results for prompt injection before forwarding them to the agent. It implements ActionInterceptor and sits between the upstream router and the policy interceptor in the chain.

In monitor mode, detections are logged but responses pass through. In enforce mode, responses containing injection patterns are blocked.

func NewResponseScanInterceptor

func NewResponseScanInterceptor(
	scanner *ResponseScanner,
	next ActionInterceptor,
	mode ScanMode,
	enabled bool,
	logger *slog.Logger,
) *ResponseScanInterceptor

NewResponseScanInterceptor creates a new ResponseScanInterceptor.

func (*ResponseScanInterceptor) Enabled

func (r *ResponseScanInterceptor) Enabled() bool

Enabled returns whether scanning is currently active.

func (*ResponseScanInterceptor) Intercept

Intercept processes a CanonicalAction through the chain and scans server-to-client responses for prompt injection.

func (*ResponseScanInterceptor) Mode

Mode returns the current scan mode.

func (*ResponseScanInterceptor) SetEnabled

func (r *ResponseScanInterceptor) SetEnabled(enabled bool)

SetEnabled updates the enabled state thread-safely.

func (*ResponseScanInterceptor) SetMode

func (r *ResponseScanInterceptor) SetMode(mode ScanMode)

SetMode updates the scan mode thread-safely.

type ResponseScanner

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

ResponseScanner detects prompt injection patterns in MCP tool results. All patterns are compiled at construction time for minimal per-scan overhead.

func NewResponseScanner

func NewResponseScanner() *ResponseScanner

NewResponseScanner creates a ResponseScanner with compiled regex patterns for detecting prompt injection attacks in tool responses.

func (*ResponseScanner) Scan

func (s *ResponseScanner) Scan(content string) ScanResult

Scan runs all compiled patterns against the given content string. Returns a ScanResult with any findings. Empty content returns immediately with no findings.

func (*ResponseScanner) ScanJSON

func (s *ResponseScanner) ScanJSON(v interface{}) ScanResult

ScanJSON recursively scans JSON-compatible values (strings, maps, slices) for prompt injection patterns. This handles the common case where MCP tool results are JSON objects with string fields that may contain injected content.

type RuleAction

type RuleAction string

RuleAction determines the action to take when a rule matches.

const (
	// RuleActionBlock blocks the action entirely.
	RuleActionBlock RuleAction = "block"
	// RuleActionAlert allows the action but raises an alert.
	RuleActionAlert RuleAction = "alert"
	// RuleActionLog allows the action but logs it.
	RuleActionLog RuleAction = "log"
)

type RuleMode

type RuleMode string

RuleMode determines whether a rule operates as a blocklist or allowlist.

const (
	// RuleModeBlocklist blocks matching targets.
	RuleModeBlocklist RuleMode = "blocklist"
	// RuleModeAllowlist allows only matching targets.
	RuleModeAllowlist RuleMode = "allowlist"
)

type ScanFinding

type ScanFinding struct {
	// PatternName is the identifier of the matched pattern (e.g., "system_prompt_override").
	PatternName string
	// PatternCategory groups related patterns (e.g., "prompt_injection", "delimiter_escape").
	PatternCategory string
	// MatchedText is the text that matched, truncated to 100 characters.
	MatchedText string
	// Position is the byte offset where the match starts in the scanned content.
	Position int
}

ScanFinding represents a single pattern match found during scanning.

type ScanMode

type ScanMode string

ScanMode controls how the response scanner handles detections.

const (
	// ScanModeMonitor logs detections without blocking responses.
	ScanModeMonitor ScanMode = "monitor"
	// ScanModeEnforce blocks responses containing prompt injection.
	ScanModeEnforce ScanMode = "enforce"
)

type ScanResult

type ScanResult struct {
	// Detected is true if one or more patterns matched.
	Detected bool
	// Findings contains all pattern matches found.
	Findings []ScanFinding
	// ScanDurationNs is how long the scan took in nanoseconds.
	ScanDurationNs int64
}

ScanResult contains the outcome of scanning content for prompt injection.

type TargetType

type TargetType string

TargetType categorizes the kind of target in an outbound rule.

const (
	// TargetDomain matches an exact domain name.
	TargetDomain TargetType = "domain"
	// TargetIP matches an exact IP address.
	TargetIP TargetType = "ip"
	// TargetCIDR matches an IP within a CIDR range.
	TargetCIDR TargetType = "cidr"
	// TargetDomainGlob matches a domain against a glob pattern, including subdomains.
	TargetDomainGlob TargetType = "domain_glob"
	// TargetPortRange matches a port within a numeric range.
	TargetPortRange TargetType = "port_range"
)

Jump to

Keyboard shortcuts

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