pins

package
v0.1.0-beta.15 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
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.

View Source
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).

View Source
const (
	ConfidenceHigh   = "high"
	ConfidenceMedium = "medium"
	ConfidenceLow    = "low"
)

Confidence levels for findings.

View Source
const (
	StatusPinned                  = "pinned"
	StatusDrift                   = "drift"
	StatusApprovedPendingRedeploy = "approved_pending_redeploy"
)

Status values for ServerPins.

View Source
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.

View Source
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

View Source
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

func HashTools(tools []mcp.Tool) (string, error)

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

func MaxSeverity(findings []Finding) string

MaxSeverity returns the highest severity present, or "" for no findings.

func SeverityRank

func SeverityRank(s string) int

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

func FilterFindings(findings []Finding, ignore []string) []Finding

FilterFindings drops findings whose code appears in ignore.

func ScanShadowing

func ScanShadowing(t mcp.Tool, self string, inventory map[string][]string) []Finding

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

func ScanTool(t mcp.Tool) []Finding

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

func New(stackName string) *PinStore

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

func NewWithPath(dir, stackName string) *PinStore

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

func (ps *PinStore) Approve(serverName string, tools []mcp.Tool) error

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

func (ps *PinStore) Load() error

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

func (ps *PinStore) Reset(serverName string) error

Reset deletes the pin record for a server. The next VerifyOrPin call will re-pin.

func (*PinStore) ScanEnabled

func (ps *PinStore) ScanEnabled() bool

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

func (ps *PinStore) ScanIgnoreCodes() []string

ScanIgnoreCodes returns a copy of the configured ignore list.

func (*PinStore) SetScanConfig

func (ps *PinStore) SetScanConfig(enabled bool, ignore []string)

SetScanConfig configures the poisoning scanner: enabled toggles it, ignore suppresses findings by code. Call before the store starts verifying.

func (*PinStore) Verify

func (ps *PinStore) Verify(serverName string, tools []mcp.Tool) (*VerifyResult, error)

Verify checks tools against stored pins without pinning new tools. Unlike VerifyOrPin, new tools are not auto-pinned.

func (*PinStore) VerifyOrPin

func (ps *PinStore) VerifyOrPin(serverName string, tools []mcp.Tool) (*VerifyResult, error)

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.

Jump to

Keyboard shortcuts

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