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(h *harness.Harness) ([]byte, error)
- func GenerateTraceID() string
- func HasCriticalFindings(findings []Finding) bool
- func HookFiles(h *harness.Harness) map[string][]byte
- func IsValidTraceID(id string) bool
- func MLScanAvailable() bool
- func ShouldScan(filename string) bool
- type ContextInjectionScanner
- type Finding
- type Pipeline
- type SSRFValidator
- type ScanResult
- type Scanner
- type SecretRedactor
- type TracedFinding
- type UnicodeNormalizer
Constants ¶
const SandboxHooksDir = "/tmp/workspace/.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, }
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.
func DestroyMLScanner ¶
func DestroyMLScanner()
DestroyMLScanner is a no-op stub when ONNX runtime is not available.
func GenerateClaudeSettings ¶
GenerateClaudeSettings produces a .claude/settings.json with security hooks configured according to the harness SecurityConfig. 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 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 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.
- 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"
Finding
}
TracedFinding is a Finding enriched with trace and phase metadata for the JSONL audit log.
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