Documentation
¶
Overview ¶
Package shield bridges the Shield safety engine to Cortex: content scans through safety.Scanner and safety.PublicationGate, streamed model output a segment at a time through safety.IncrementalGate, and tool-call scans layered under the host's cortex.ToolAuthorizer (see NewToolAuthorizer).
Every scan runs under a shield tenant taken from the run's scope by a cortex.TenantFunc, and under a shield app. A scan that cannot run (no tenant, no profile for the tenant, an invalid profile, input over shield's size limit) fails closed: content is not published and a tool call is denied. Errors carry only shield's sentinel, never the scanned text or the shield error's own detail.
Index ¶
- Variables
- func EngineOption(c vessel.Vessel, opts ...Option) engine.Option
- func NewToolAuthorizer(eng *shieldengine.Engine, host cortex.ToolAuthorizer, opts ToolCallOptions) cortex.ToolAuthorizer
- type Adapter
- func (a *Adapter) Check(ctx context.Context, req safety.PublicationRequest) (safety.PublicationDecision, error)
- func (a *Adapter) NewSegmentChecker(ctx context.Context, req safety.PublicationRequest) (safety.SegmentChecker, error)
- func (a *Adapter) ScanInput(ctx context.Context, req *safety.ScanRequest) (*safety.ScanResult, error)
- func (a *Adapter) ScanOutput(ctx context.Context, req *safety.ScanRequest) (*safety.ScanResult, error)
- type Option
- type ToolCallOptions
Constants ¶
This section is empty.
Variables ¶
var ErrToolCallBlocked = errors.New("shield: tool call blocked")
ErrToolCallBlocked is the denial a tool call gets when shield blocks it. The denial adds shield's reason, which is the message of the finding that blocked the call and never holds argument content.
ErrToolScanUnavailable is the denial a tool call gets when shield cannot scan it: no tenant, no profile, an invalid profile, arguments that are not JSON or that are too large. The denial is joined with the shield sentinel the scan failed with, and nothing else of the scan's error.
Functions ¶
func EngineOption ¶
EngineOption returns an engine.Option that auto-discovers a Shield engine from the DI container and installs the adapter as both the engine's safety scanner and its publication gate. If no shield engine is found, it returns a no-op option (safe to always include).
func NewToolAuthorizer ¶ added in v1.10.0
func NewToolAuthorizer(eng *shieldengine.Engine, host cortex.ToolAuthorizer, opts ToolCallOptions) cortex.ToolAuthorizer
NewToolAuthorizer layers shield's tool-call scan under the host's authorizer and combines the two with scan.Stricter, so it can deny or require approval but never allow what the host refused.
The host answers first. A host denial is returned as it is, and shield is not asked. Otherwise the call is scanned and the stricter answer wins: a shield block denies the call with ErrToolCallBlocked, and a scan that cannot run denies it with ErrToolScanUnavailable. An approval the host asked for comes back as the host's own error, so its reason and TTL hold. One only shield asked for is a cortex.ApprovalRequired whose reason starts "shield: " and whose TTL is opts.ApprovalTTL.
A nil host allows every call and shows every tool, so shield decides alone. Visible always defers to the host: shield only gates calls.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter implements safety.Scanner, safety.PublicationGate and safety.IncrementalGate by delegating to a shield engine.
func New ¶
func New(eng *shieldengine.Engine, opts ...Option) *Adapter
New creates a Shield adapter over eng.
func (*Adapter) Check ¶ added in v1.10.0
func (a *Adapter) Check(ctx context.Context, req safety.PublicationRequest) (safety.PublicationDecision, error)
Check screens content for publication. It scans in the request's direction under the request's profile, telling shield where the text came from. Inbound text from an untrusted source (see WithUntrustedSources) is marked untrusted, so unless shield blocks it, shield's quoted text is what gets published, with status redacted. The outbound pass over the same text is not marked again, which would quote it twice.
Quoting is a fixed point. Inbound untrusted text that is already exactly one quote for its source, the way shield writes it, is not wrapped again: its inner text is scanned as untrusted, and when shield changes nothing the same bytes come back. The engine relies on this when it re-checks a prepared run's knowledge before every pass. Anything else, a forged or partial wrapper included, is quoted as usual, and shield escapes the tags inside it. An untrusted result that is not one quote is refused.
The result maps as safety.DecisionFromScan says. A scan that cannot run fails closed with safety.ErrUnavailable.
func (*Adapter) NewSegmentChecker ¶ added in v1.10.0
func (a *Adapter) NewSegmentChecker(ctx context.Context, req safety.PublicationRequest) (safety.SegmentChecker, error)
NewSegmentChecker opens a checker for one stream of model output, so the engine can publish shield-approved text as it arrives (safety.ModeCheckedSegments). req is the stream's output request: its profile, app, agent and run apply to every segment, and its content is ignored. The tenant comes from the scope on ctx, as for every scan, and the checker refuses to open without one.
Each check approves a prefix of the pending text: through the last line break, or through the last sentence end (".", "!" or "?" followed by a space or a line break). A colon is not a sentence end, so a line such as "password: value" reaches shield in one scan. With neither, the pending text is held until it reaches the segment limit (see WithSegmentLimit), and is then cut after its last whitespace that does not sit next to a ':' or '=' (a key and its value stay together); a single word is never split. A PEM block is held from the start of its "-----BEGIN " line until its matching "-----END " line arrives, so shield sees the whole block in one scan; a block that never closes is held until the final check, and the engine's output limit bounds the hold. A final check takes everything left.
Shield still sees each segment on its own. A rule that needs text from more than one segment does not see it, and approved text is already published, so a host that can't accept any partial text streamed under these rules should use buffered publication.
The prefix is scanned with ScanOutput exactly as Check scans model output, and its decision maps as safety.DecisionFromScan says. A scan that cannot run fails closed with safety.ErrUnavailable, a blocked segment returns safety.ErrBlocked, and either one ends the stream: every later check fails the same way, so text held back is never published. Errors never carry the pending text.
func (*Adapter) ScanInput ¶
func (a *Adapter) ScanInput(ctx context.Context, req *safety.ScanRequest) (*safety.ScanResult, error)
ScanInput scans input content via shield.
func (*Adapter) ScanOutput ¶
func (a *Adapter) ScanOutput(ctx context.Context, req *safety.ScanRequest) (*safety.ScanResult, error)
ScanOutput scans output content via shield.
type Option ¶ added in v1.10.0
type Option func(*Adapter)
Option configures an Adapter.
func WithApp ¶ added in v1.10.0
WithApp sets the shield app every scan runs under, which together with the tenant selects the safety profiles. The default is the request's AppID, which the engine takes from the scope's "app" level or, without one, the canonical scope.
func WithSegmentLimit ¶ added in v1.10.0
WithSegmentLimit sets how many bytes of streamed model output a segment checker holds before it cuts a run of text with no sentence or line end at a whitespace, never one next to an assignment operator. The default is 2048. A value of zero or less keeps the default. See NewSegmentChecker.
func WithTenant ¶ added in v1.10.0
func WithTenant(f cortex.TenantFunc) Option
WithTenant sets how the adapter names the shield tenant a scan runs under, from the scope on the context. The default is cortex.RootTenant, the value of the scope's first level. An error or an empty result fails the scan closed with shield.ErrTenantRequired.
func WithUntrustedSources ¶ added in v1.10.0
WithUntrustedSources replaces the publication sources whose inbound text shield treats as untrusted. The default is "tool_result", "tool_error" and "retrieval". Shield quotes untrusted text it does not block, so the model reads it as data, and the gate publishes the quoted text.
type ToolCallOptions ¶ added in v1.10.0
type ToolCallOptions struct {
// RiskClass is the host's own class for a call, one of shield's
// presets.Risk* strings for the InfraAgent tools profile. Nil, or an
// empty answer, is shield's unknown class, which that profile blocks.
RiskClass func(ctx context.Context, s cortex.Subject, call llm.ToolCall) string
// Profile names the shield profile tool calls are scanned under, such
// as presets.InfraAgentTools. Empty uses shield's default profile.
Profile string
// ApprovalTTL is the decision deadline on an approval only shield
// asked for. Zero uses the engine's suspension TTL. An approval the
// host asked for keeps the host's own TTL.
ApprovalTTL time.Duration
// Tenant names the shield tenant from the run's scope. Nil is
// cortex.RootTenant.
Tenant cortex.TenantFunc
// App is the shield app. Empty is the scope's "app" level or, without
// one, the canonical scope, the same app the engine gives content
// scans.
App string
}
ToolCallOptions configures NewToolAuthorizer.