Documentation
¶
Index ¶
- Constants
- Variables
- func HashTools(tools []mcp.Tool) (string, error)
- func MaxSeverity(findings []Finding) string
- func SeverityRank(s string) int
- type Finding
- type GatewayAdapter
- type PinFile
- type PinRecord
- type PinStore
- func (ps *PinStore) Approve(serverName string, tools []mcp.Tool) error
- func (ps *PinStore) GetAll() map[string]*ServerPins
- func (ps *PinStore) GetServer(serverName string) (*ServerPins, bool)
- func (ps *PinStore) Load() error
- func (ps *PinStore) Reset(serverName string) error
- func (ps *PinStore) ScanEnabled() bool
- func (ps *PinStore) ScanIgnoreCodes() []string
- func (ps *PinStore) SetScanConfig(enabled bool, ignore []string)
- func (ps *PinStore) Verify(serverName string, tools []mcp.Tool) (*VerifyResult, error)
- func (ps *PinStore) VerifyOrPin(serverName string, tools []mcp.Tool) (*VerifyResult, error)
- type ServerPins
- type ToolDiff
- type VerifyResult
Constants ¶
const ( CodeHiddenInstructions = "P001" // hidden-instruction phrases CodeSensitiveFiles = "P002" // sensitive file references CodeSensitiveActions = "P003" // sensitive-action language CodeSuspiciousWords = "P004" // suspicious emphasis words CodeHiddenUnicode = "P005" // invisible characters, decoded payloads CodeToolShadowing = "P006" // references to other servers' tools )
Finding codes. Stable identifiers usable in scan_ignore.
const ( SeverityInfo = "info" SeverityWarn = "warn" SeverityCritical = "critical" )
Severity levels for findings, matching the info/warn/critical vocabulary used across gridctl (pkg/optimize, the web UI severity cards).
const ( ConfidenceHigh = "high" ConfidenceMedium = "medium" ConfidenceLow = "low" )
Confidence levels for findings.
const ( StatusPinned = "pinned" StatusDrift = "drift" StatusApprovedPendingRedeploy = "approved_pending_redeploy" )
Status values for ServerPins.
const ( VerifyStatusPinned = "pinned" // first pin, just stored VerifyStatusVerified = "verified" // hashes match VerifyStatusDrift = "drift" // tool hashes changed VerifyStatusNewTools = "new_tools" // server added tools (no drift, auto-pinned) VerifyStatusRemovedTools = "removed_tools" // server removed tools (warning only) )
Verify status values returned in VerifyResult.
const ( ChangeKindDescription = "description" ChangeKindInputSchema = "input_schema" ChangeKindOutputSchema = "output_schema" ChangeKindSchemaUncaptured = "schema_uncaptured" )
Change kinds carried on ToolDiff.ChangeKinds, describing which parts of a tool's definition moved. ChangeKindSchemaUncaptured marks the legacy state where the pin predates schema capture: the old schemas are unrecoverable, so the hash move may include a schema change that cannot be shown. It is reported alongside description when the prose also moved.
Variables ¶
var ( ErrCorrupt = errors.New("corrupt pin file") ErrNewerVersion = errors.New("pin file written by a newer gridctl") )
Sentinel errors reported by Load. A caller that prefers availability over strictness (the daemon) may match ErrCorrupt and continue with the empty store Load leaves behind; ErrNewerVersion must never be papered over, or this binary would re-pin from scratch and overwrite the newer file.
Functions ¶
func HashTools ¶
HashTools computes the server-level fingerprint that Approve would store for the given tools. It lets callers bind an approval to a reviewed snapshot: capture the fingerprint when rendering a diff, then compare it against the live one at approve time and reject on mismatch, closing the window where a server swaps in unreviewed definitions between review and approval.
func MaxSeverity ¶
MaxSeverity returns the highest severity present, or "" for no findings.
func SeverityRank ¶
SeverityRank orders finding severities for threshold comparison: critical outranks warn outranks info; unknown values rank lowest. Exported because severity ordering is a property of the finding vocabulary, and callers (the CLI's --fail-on-findings gate) should consume it rather than re-encode it.
Types ¶
type Finding ¶
type Finding struct {
Code string `json:"code"`
Severity string `json:"severity"`
Confidence string `json:"confidence"`
Field string `json:"field"`
Snippet string `json:"snippet,omitempty"`
Message string `json:"message"`
Decoded string `json:"decoded,omitempty"`
}
Finding is one poisoning signal detected in a tool definition. Snippet is raw matched text: every rendering surface must escape it (the CLI and web UI both route it through their escapeNonPrintable helpers).
func FilterFindings ¶
FilterFindings drops findings whose code appears in ignore.
func ScanShadowing ¶
ScanShadowing implements P006: a tool description that names another server's tools (or the server itself) can steer the model's use of that trusted server. inventory maps server name to tool names; self is excluded. The rules, in match order per sibling server:
- Candidate names shorter than five characters are never matched; longer ones match case-insensitively on word boundaries (no per-name regexp compilation: this runs per tool on the pins read path), against the base normalization so literal digit-bearing names ("route53") are not corrupted by the leetspeak fold.
- A distinctive tool name ("create_issue") flags warn/medium on its own.
- A tool name in genericToolNames ("search", "fetch") flags warn/medium only when the owning server's name also appears as a whole word (a qualified reference like "route atlassian search through this"); unqualified generic verbs are ordinary prose and emit nothing. The qualifier is subject to the same length gate: a short server alias ("web", "api") is itself ordinary prose and must not turn "search the web" back into a finding.
- The server name alone, with no tool-name hit, is a cross-server mention rather than steering: info/low, visible on tool cards but excluded from the findings chip and toast (countFindingServers filters to warn+).
At most one finding is emitted per referenced server.
func ScanTool ¶
ScanTool runs all pin-time checks (P001-P005) over one tool definition: name, description, and the decoded string values of both schemas (injection also hides in parameter names, enums, and defaults, so schema keys are scanned too). P006 needs the cross-server inventory and lives in ScanShadowing.
type GatewayAdapter ¶
type GatewayAdapter struct {
// contains filtered or unexported fields
}
GatewayAdapter wraps PinStore to implement mcp.SchemaVerifier. It bridges the pins package to the gateway without creating an import cycle: pkg/pins already imports pkg/mcp for the Tool type, so pkg/mcp cannot import pkg/pins in return. The gateway holds a mcp.SchemaVerifier interface, and callers wire it with a GatewayAdapter at startup.
func NewGatewayAdapter ¶
func NewGatewayAdapter(ps *PinStore) *GatewayAdapter
NewGatewayAdapter creates a GatewayAdapter backed by the given PinStore.
func (*GatewayAdapter) ResetServerPins ¶
func (a *GatewayAdapter) ResetServerPins(serverName string) error
ResetServerPins implements mcp.PinResetter. It deletes the pin record for serverName so the next VerifyOrPin re-pins from scratch.
func (*GatewayAdapter) VerifyOrPin ¶
func (a *GatewayAdapter) VerifyOrPin(serverName string, tools []mcp.Tool) ([]mcp.SchemaDrift, error)
VerifyOrPin implements mcp.SchemaVerifier. It delegates to PinStore.VerifyOrPin and converts the result into the mcp.SchemaDrift slice that the gateway consumes.
type PinFile ¶
type PinFile struct {
Version string `json:"version"`
Stack string `json:"stack"`
CreatedAt time.Time `json:"created_at"`
Servers map[string]*ServerPins `json:"servers"`
}
PinFile is the top-level JSON structure stored at ~/.gridctl/pins/{stackName}.json.
type PinRecord ¶
type PinRecord struct {
Hash string `json:"hash"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
PinnedAt time.Time `json:"pinned_at"`
Findings []Finding `json:"findings,omitempty"`
InputSchema string `json:"input_schema,omitempty"`
OutputSchema string `json:"output_schema,omitempty"`
}
PinRecord holds the hash and metadata for a single tool definition. Description is stored to enable human-readable diff output on drift. Findings are the poisoning-scan results captured when the tool was pinned; they are derived, advisory data (an older gridctl rewriting the file simply drops them, so their presence does not require a file-version bump). InputSchema and OutputSchema hold the canonical serialization of the pinned schemas so a schema-only drift can show what changed; like Findings they are derived data (an older gridctl rewriting the file drops them, and pins recorded before schema capture simply lack them), so no file-version bump.
type PinStore ¶
type PinStore struct {
// contains filtered or unexported fields
}
PinStore manages TOFU schema pins for a deployed stack. It is safe for concurrent use: in-memory access is guarded by a RWMutex, and disk writes are serialized via state.WithLock.
func New ¶
New creates a PinStore for the given stack name. The pin file lives at ~/.gridctl/pins/{stackName}.json. Call Load() before performing verification or pinning operations.
func NewWithPath ¶
NewWithPath creates a PinStore that stores pins in dir/{stackName}.json. Intended for testing where the real state directory should not be used.
func (*PinStore) Approve ¶
Approve re-pins the current tool definitions for a server, clearing drift.
func (*PinStore) GetAll ¶
func (ps *PinStore) GetAll() map[string]*ServerPins
GetAll returns a deep-copied snapshot of all server pin records. Callers iterate and marshal the result outside the store's lock while the gateway's verify path mutates records in place (scheme upgrades, re-pins), so shared pointers would be a data race and shared maps a runtime panic.
func (*PinStore) GetServer ¶
func (ps *PinStore) GetServer(serverName string) (*ServerPins, bool)
GetServer returns a deep-copied pin record for a single server; see GetAll for why a copy.
func (*PinStore) Load ¶
Load reads the pin file from disk into memory. If the file does not exist, the store starts empty (ready for first pin). A file that cannot be parsed returns an error wrapping ErrCorrupt, and a file written by a newer gridctl returns one wrapping ErrNewerVersion; in both cases the in-memory store is reset to empty so a caller that chooses to continue anyway has a usable store.
func (*PinStore) Reset ¶
Reset deletes the pin record for a server. The next VerifyOrPin call will re-pin.
func (*PinStore) ScanEnabled ¶
ScanEnabled reports whether the poisoning scanner is on for this store. Callers that compute supplementary findings outside the store (the API layer's cross-server shadowing check) use this to honor the same config.
func (*PinStore) ScanIgnoreCodes ¶
ScanIgnoreCodes returns a copy of the configured ignore list.
func (*PinStore) SetScanConfig ¶
SetScanConfig configures the poisoning scanner: enabled toggles it, ignore suppresses findings by code. Call before the store starts verifying.
func (*PinStore) Verify ¶
Verify checks tools against stored pins without pinning new tools. Unlike VerifyOrPin, new tools are not auto-pinned.
func (*PinStore) VerifyOrPin ¶
VerifyOrPin is the primary entry point called on RefreshTools. On first use it pins the tools; on subsequent calls it verifies against pins. New tools not in pins are auto-pinned; modified tools trigger VerifyStatusDrift.
type ServerPins ¶
type ServerPins struct {
ServerHash string `json:"server_hash"`
PinnedAt time.Time `json:"pinned_at"`
LastVerifiedAt time.Time `json:"last_verified_at"`
ToolCount int `json:"tool_count"`
Status string `json:"status"`
Tools map[string]*PinRecord `json:"tools"`
}
ServerPins holds the pin state for a single MCP server.
type ToolDiff ¶
type ToolDiff struct {
Name string
OldHash string
NewHash string
OldDescription string
NewDescription string
OldInputSchema string
NewInputSchema string
OldOutputSchema string
NewOutputSchema string
ChangeKinds []string
Findings []Finding
}
ToolDiff describes a change in a single tool's definition. Findings are poisoning-scan results for the NEW definition, computed at verify time so the reviewer sees them beside the diff they annotate. Schema fields carry canonical serializations; Old* are empty for pins recorded before schema capture (see ChangeKindSchemaUncaptured).
type VerifyResult ¶
type VerifyResult struct {
ServerName string
Status string
ModifiedTools []ToolDiff
NewTools []string
RemovedTools []string
}
VerifyResult contains the result of a VerifyOrPin or Verify call.
func (*VerifyResult) HasDrift ¶
func (r *VerifyResult) HasDrift() bool
HasDrift returns true if any pinned tools have changed hashes.