Documentation
¶
Overview ¶
Package security provides input sanitization and output scanning for fullsend's agent entrypoints. These scanners run at the boundaries:
- Input boundary: before untrusted text (issue bodies, PR descriptions, code comments, context files) reaches agent processing
- Output boundary: before agent-generated text (PR comments, issue comments) is posted via the forge API
The scanners are adapted from Hermes Agent's production security controls, ported to Go for integration into fullsend's CLI entrypoints.
See: experiments/hermes-security-patterns/ for the Python prototypes and evaluation results.
Index ¶
- Constants
- Variables
- func AppendFinding(path string, tf TracedFinding) error
- func DestroyMLScanner()
- func GenerateClaudeSettings(hooks ClaudeSandboxHooks) ([]byte, error)
- func GenerateTraceID() string
- func HasCriticalFindings(findings []Finding) bool
- func HookFiles(hooks ClaudeSandboxHooks) map[string][]byte
- func IsShellSafeTraceID(id string) bool
- func IsValidTraceID(id string) bool
- func MLScanAvailable() bool
- func ShouldScan(filename string) bool
- type ChainVerification
- type ClaudeSandboxHooks
- type ContextInjectionScanner
- type Finding
- type Pipeline
- type SSRFValidator
- type ScanResult
- type Scanner
- type SecretRedactor
- type TracedFinding
- type UnicodeNormalizer
Constants ¶
const SandboxHooksDir = sandbox.SandboxWorkspace + "/.claude/hooks"
SandboxHooksDir is the path where hook scripts are installed inside the sandbox. Must match sandbox.SandboxWorkspace + "/.claude/hooks".
Variables ¶
var CanaryPostToolHook []byte
var CanaryPreToolHook []byte
var ContextSuppressPostToolHook []byte
var SSRFPreToolHook []byte
var ScannableFiles = map[string]bool{ "agents.md": true, ".cursorrules": true, "claude.md": true, ".claude.md": true, "soul.md": true, ".hermes.md": true, "hermes.md": true, "gemini.md": true, ".gemini.md": true, "copilot-instructions.md": true, "skill.md": true, "plugin.json": true, ".lsp.json": true, }
ScannableFiles is the set of filenames that should be scanned for prompt injection before being loaded into agent context.
var SecretRedactPostToolHook []byte
var TirithCheckHook []byte
var ToolAllowlistPreToolHook []byte
var UnicodePostToolHook []byte
Functions ¶
func AppendFinding ¶
func AppendFinding(path string, tf TracedFinding) error
AppendFinding writes a traced finding as a JSON line to the given file path. It computes a SHA-256 hash chain: each entry's hash covers the previous entry's hash and the current entry's payload, making the log tamper-evident.
func DestroyMLScanner ¶
func DestroyMLScanner()
DestroyMLScanner is a no-op stub when ONNX runtime is not available.
func GenerateClaudeSettings ¶
func GenerateClaudeSettings(hooks ClaudeSandboxHooks) ([]byte, error)
GenerateClaudeSettings produces a .claude/settings.json with security hooks configured according to hooks. Returns the JSON bytes.
func GenerateTraceID ¶
func GenerateTraceID() string
GenerateTraceID returns a UUID v4 string for correlating security findings across scanning phases (host pre-step, sandbox pre-agent, runtime hooks, post-agent output scan).
func HasCriticalFindings ¶ added in v0.2.0
HasCriticalFindings reports whether any finding has critical severity.
func HookFiles ¶
func HookFiles(hooks ClaudeSandboxHooks) map[string][]byte
HookFiles returns a map of filename -> content for all enabled hook scripts.
func IsShellSafeTraceID ¶ added in v0.28.0
IsShellSafeTraceID returns true if the trace ID consists only of lowercase hex and dashes in UUID shape — the property that makes it safe for shell interpolation. Unlike IsValidTraceID it accepts any version/variant, because a trace id adopted from an inbound W3C traceparent (issue #2779) is not necessarily UUID v4. The charset restriction is the entire safety argument; version bits carry no interpolation risk.
func IsValidTraceID ¶
IsValidTraceID returns true if the trace ID is safe for shell interpolation.
func MLScanAvailable ¶
func MLScanAvailable() bool
MLScanAvailable reports whether the native ONNX ML scanner is compiled in.
func ShouldScan ¶
ShouldScan reports whether a filename should be scanned for injection.
Types ¶
type ChainVerification ¶ added in v0.19.0
type ChainVerification struct {
Valid bool
Entries int
BrokenAt int // 0-indexed; -1 if valid
BrokenMsg string // empty if valid
}
ChainVerification holds the result of verifying a findings JSONL file.
func VerifyChain ¶ added in v0.19.0
func VerifyChain(path string) (ChainVerification, error)
VerifyChain reads a findings JSONL file and verifies the hash chain integrity. Returns a ChainVerification indicating whether the chain is intact. Entries without hash fields (from older versions) are skipped.
type ClaudeSandboxHooks ¶ added in v0.14.0
type ClaudeSandboxHooks struct {
// contains filtered or unexported fields
}
ClaudeSandboxHooks configures Claude Code PreToolUse/PostToolUse security hooks. A nil internal hook config uses the same defaults as an unset harness security block.
func ClaudeSandboxHooksFromHarness ¶ added in v0.14.0
func ClaudeSandboxHooksFromHarness(h *harness.Harness) ClaudeSandboxHooks
ClaudeSandboxHooksFromHarness extracts sandbox hook settings from a harness.
func (ClaudeSandboxHooks) TirithFailOn ¶ added in v0.14.0
func (c ClaudeSandboxHooks) TirithFailOn() string
TirithFailOn returns the Tirith severity threshold env value, or empty when unset.
func (ClaudeSandboxHooks) TirithRequired ¶ added in v0.14.0
func (c ClaudeSandboxHooks) TirithRequired() bool
TirithRequired reports whether Tirith Bash scanning should be required in the sandbox.
type ContextInjectionScanner ¶
type ContextInjectionScanner struct {
// contains filtered or unexported fields
}
ContextInjectionScanner detects prompt injection patterns in project context files (AGENTS.md, .cursorrules, CLAUDE.md, etc.) before they are loaded into an agent's system prompt. Adapted from Hermes Agent's prompt_builder.py injection scanning.
func NewContextInjectionScanner ¶
func NewContextInjectionScanner() *ContextInjectionScanner
NewContextInjectionScanner creates a scanner with the default pattern set.
func (*ContextInjectionScanner) Name ¶
func (c *ContextInjectionScanner) Name() string
func (*ContextInjectionScanner) Scan ¶
func (c *ContextInjectionScanner) Scan(text string) ScanResult
type Finding ¶
type Finding struct {
Scanner string // "secret_redactor", "ssrf_validator", "context_injection", "unicode_normalizer"
Name string // pattern name or category
Severity string // "critical", "high", "medium"
Detail string // human-readable description
Position int // byte offset in original text, -1 if N/A
}
Finding represents a single security issue detected by a scanner.
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline chains multiple scanners in sequence. Each scanner's sanitized output feeds into the next scanner's input.
func InputPipeline ¶
func InputPipeline() *Pipeline
InputPipeline returns the standard input scanning pipeline for untrusted text entering the agent. Order matters:
- UnicodeNormalizer — strip invisible chars, normalize fullwidth
- ContextInjectionScanner — detect prompt injection patterns
func NewPipeline ¶
NewPipeline creates a scanner pipeline from the given scanners. Scanners run in order; place normalizers first, detectors after.
func OutputPipeline ¶
func OutputPipeline() *Pipeline
OutputPipeline returns the standard output scanning pipeline for agent-generated text before posting to the forge.
- UnicodeNormalizer — strip invisible chars, normalize fullwidth
- SecretRedactor — redact API keys, tokens, credentials
func (*Pipeline) Scan ¶
func (p *Pipeline) Scan(text string) ScanResult
Scan runs all scanners in sequence. Returns the aggregate result. The pipeline is fail-open for sanitization (each scanner transforms the text) but fail-closed for safety (any scanner marking unsafe makes the whole result unsafe).
type SSRFValidator ¶
type SSRFValidator struct {
// contains filtered or unexported fields
}
SSRFValidator validates URLs against blocked networks, hostnames, and schemes to prevent Server-Side Request Forgery. Adapted from Hermes Agent's URL validation.
func NewSSRFValidator ¶
func NewSSRFValidator() *SSRFValidator
NewSSRFValidator creates a validator with the default blocklists.
func (*SSRFValidator) Name ¶
func (s *SSRFValidator) Name() string
func (*SSRFValidator) Scan ¶
func (s *SSRFValidator) Scan(text string) ScanResult
Scan implements the Scanner interface. Extracts URLs from text and validates each one. Uses regex-based extraction (matching the Python hook's URL_PATTERN) to handle URLs embedded in markdown, JSON, etc.
func (*SSRFValidator) ValidateRedirectChain ¶
func (s *SSRFValidator) ValidateRedirectChain(urls []string) ScanResult
ValidateRedirectChain checks each URL in a redirect chain.
func (*SSRFValidator) ValidateURL ¶
func (s *SSRFValidator) ValidateURL(rawURL string, resolveDNS bool) ScanResult
ValidateURL checks a single URL for SSRF risk. Set resolveDNS to true to resolve hostnames and check resolved IPs.
type ScanResult ¶
type ScanResult struct {
Safe bool
Findings []Finding
Sanitized string // cleaned/redacted version of input (empty if unchanged)
}
ScanResult holds the outcome of a security scan.
func RunMLScan ¶
func RunMLScan(_ string, _ bool) ScanResult
RunMLScan is a no-op stub when ONNX runtime is not available.
type Scanner ¶
type Scanner interface {
// Name returns the scanner identifier.
Name() string
// Scan checks text for security issues. Returns a ScanResult with
// findings and optionally a sanitized version of the input.
Scan(text string) ScanResult
}
Scanner is the interface for all security scanners.
type SecretRedactor ¶
type SecretRedactor struct {
// contains filtered or unexported fields
}
SecretRedactor scans text for API keys, tokens, credentials, and sensitive patterns, replacing them with masked versions. Adapted from Hermes Agent's agent/redact.py.
func NewSecretRedactor ¶
func NewSecretRedactor() *SecretRedactor
NewSecretRedactor creates a redactor with the default pattern set.
func (*SecretRedactor) Name ¶
func (s *SecretRedactor) Name() string
func (*SecretRedactor) Scan ¶
func (s *SecretRedactor) Scan(text string) ScanResult
type TracedFinding ¶
type TracedFinding struct {
TraceID string `json:"trace_id"`
Timestamp string `json:"timestamp"`
Phase string `json:"phase"` // "host_input", "sandbox_context", "hook_pretool", "hook_posttool", "host_output"
PrevHash string `json:"prev_hash"`
Hash string `json:"hash"`
Finding
}
TracedFinding is a Finding enriched with trace and phase metadata for the JSONL audit log. PrevHash and Hash form a SHA-256 chain: each entry's Hash covers PrevHash and the rest of the entry, making tampering detectable.
type UnicodeNormalizer ¶
type UnicodeNormalizer struct{}
UnicodeNormalizer strips invisible Unicode characters and normalizes fullwidth characters to prevent command obfuscation and hidden payload injection. Adapted from Hermes Agent's approval.py.
func NewUnicodeNormalizer ¶
func NewUnicodeNormalizer() *UnicodeNormalizer
NewUnicodeNormalizer creates a UnicodeNormalizer.
func (*UnicodeNormalizer) Name ¶
func (u *UnicodeNormalizer) Name() string
func (*UnicodeNormalizer) Scan ¶
func (u *UnicodeNormalizer) Scan(text string) ScanResult