Documentation
¶
Index ¶
- Constants
- Variables
- func CheckAllSymlinks(configDir string) []string
- func CheckFileSecurity(relativePath string, fileContent string, isNew bool, isChanged bool, ...) (updatedSecurityConcerns []string, updatedIgnoredSecurityConcerns []string, ...)deprecated
- func CheckSymlinkSafety(path string, configDir string) string
- func DetectSecurityConcerns(content string) ([]string, map[string]string)
- func DetectSecurityConcernsWithContext(content, filePath string) ([]string, map[string]string)
- func GetDirMode(path string) (string, error)
- func GetFileMode(path string) (string, error)
- func GetPermissionError(path string, expectedMode os.FileMode) error
- func IsGroupReadable(path string) (bool, error)
- func IsWorldReadable(path string) (bool, error)
- func RunStartupCheck(configDir string) bool
- func SetGlobalApprovalManager(mgr *ApprovalManager)deprecated
- type ApprovalDecision
- type ApprovalKind
- type ApprovalManager
- func (am *ApprovalManager) RequestApproval(eventBus *events.EventBus, req ApprovalRequest) bool
- func (am *ApprovalManager) RequestApprovalDecision(eventBus *events.EventBus, req ApprovalRequest) ApprovalDecision
- func (am *ApprovalManager) RequestApprovalDecisionWithOutcome(eventBus *events.EventBus, req ApprovalRequest) (ApprovalDecision, ApprovalOutcome)
- func (am *ApprovalManager) RequestPrompt(eventBus *events.EventBus, userID, prompt string, defaultResponse bool, ...) bool
- func (am *ApprovalManager) RequestToolApproval(eventBus *events.EventBus, ...) bool
- func (am *ApprovalManager) RequestToolApprovalDecision(eventBus *events.EventBus, ...) ApprovalDecision
- func (am *ApprovalManager) RequestToolApprovalDecisionWithOutcome(eventBus *events.EventBus, ...) (ApprovalDecision, ApprovalOutcome)
- func (am *ApprovalManager) RequestToolApprovalWithOutcome(eventBus *events.EventBus, ...) (bool, ApprovalOutcome)
- func (am *ApprovalManager) RespondToApproval(requestID string, response bool) bool
- func (am *ApprovalManager) RespondToApprovalDecision(requestID string, decision ApprovalDecision) bool
- func (am *ApprovalManager) RespondToPrompt(requestID string, response bool) bool
- func (am *ApprovalManager) SetApprovalTimeout(d time.Duration)
- func (am *ApprovalManager) SetPromptTimeout(d time.Duration)
- func (am *ApprovalManager) SetTimeout(d time.Duration)
- type ApprovalOutcome
- type ApprovalRequest
- type DetectedSecret
- type ElevationGate
- type OutputRedactor
- type PermissionChecker
- type RedactionResult
- type SecretAction
- type SecretPrompter
Constants ¶
const DefaultTimeout = 30 * time.Minute
DefaultTimeout is the maximum time a request will block waiting for a webui response before applying the fallback (reject for tool kind, defaultResponse for prompt kind).
Set generously (30 min) so a user who steps away from an interactive session can still return and approve — a false-deny after 5 minutes was a recurring UX complaint. Non-interactive runs should not hit this path at all (they fast-fail before calling the manager), so the long timeout has no downside for automation.
Variables ¶
var ( // SetGlobalPromptManager is a backward-compatible alias for SetGlobalApprovalManager. SetGlobalPromptManager = SetGlobalApprovalManager // GetGlobalPromptManager is a backward-compatible alias for GetGlobalApprovalManager. GetGlobalPromptManager = GetGlobalApprovalManager )
Backward-compatible aliases so existing code referencing the old names continues to compile during the migration window.
Functions ¶
func CheckAllSymlinks ¶
CheckAllSymlinks checks all files in the config directory for symlinks pointing outside.
func CheckFileSecurity
deprecated
func CheckFileSecurity( relativePath string, fileContent string, isNew bool, isChanged bool, existingSecurityConcerns []string, existingIgnoredSecurityConcerns []string, cfg *configuration.Config, eventBus *events.EventBus, userID string, promptManager *ApprovalManager, ) ( updatedSecurityConcerns []string, updatedIgnoredSecurityConcerns []string, skipLLMSummarization bool, )
CheckFileSecurity analyzes a file's content for security concerns, prompts the user for confirmation on new detections, and returns the updated lists of security concerns and ignored concerns, along with a boolean indicating if local summarization should be skipped.
Deprecated: This function has no callers. Use Agent.CheckFileContentSecurity instead, which uses the injected ApprovalManager.
func CheckSymlinkSafety ¶
CheckSymlinkSafety checks if a path is a symlink and warns if it points outside the config dir.
func DetectSecurityConcerns ¶
DetectSecurityConcerns analyzes content for security-related patterns and returns a sorted, deduplicated list of concern types plus a map from each concern type to its first matched snippet.
func DetectSecurityConcernsWithContext ¶
DetectSecurityConcernsWithContext analyzes content with file-path context to reduce false positives. The path is consulted via isTestFile to apply stricter filtering in obvious test/example files.
func GetDirMode ¶
GetDirMode returns the permission bits of a directory as an octal string.
func GetFileMode ¶
GetFileMode returns the permission bits of a file as an octal string.
func GetPermissionError ¶
GetPermissionError returns a descriptive error for common permission issues.
func IsGroupReadable ¶
IsGroupReadable returns true if the file has group-readable permissions.
func IsWorldReadable ¶
IsWorldReadable returns true if the file has world-readable permissions.
func RunStartupCheck ¶
RunStartupCheck performs a full permission check at startup and logs warnings.
First call attempts to fix insecure permissions automatically, then checks again and warns only about issues that persist after the fix attempt. Subsequent calls return the cached result (deduplication across callers). The configDir argument is only consulted on the first call; both the CLI and WebUI callers pass configuration.GetConfigDir(), so this is safe. Returns true if any warnings were issued.
func SetGlobalApprovalManager
deprecated
func SetGlobalApprovalManager(mgr *ApprovalManager)
SetGlobalApprovalManager sets the global singleton (called by webui setup).
Deprecated: use dependency injection via Agent.InjectWebUIManagers instead.
Types ¶
type ApprovalDecision ¶
type ApprovalDecision int
ApprovalDecision is the user's choice from the 4-option approval dialog (SP-058 follow-up). The classic yes/no path collapses to ApprovalDeny or ApprovalApproveOnce so legacy bool wrappers still work; the two extra outcomes — ApprovalApproveAlways and ApprovalElevate — power the "always approve this command" and "elevate session permissions" buttons added to the WebUI dialog and the CLI prompt.
const ( // ApprovalDeny rejects the operation. Caller surfaces a security error. ApprovalDeny ApprovalDecision = iota // ApprovalApproveOnce approves this single invocation. Subsequent // invocations of the same command still prompt. ApprovalApproveOnce // ApprovalApproveAlways approves this invocation AND persists the // exact command string to Config.ApprovedShellCommands so future // runs (across restarts) skip the prompt for the same literal command. ApprovalApproveAlways // ApprovalElevate approves this invocation AND sets the agent's // risk profile override to "permissive" for the rest of this // session. Critical-tier ops still block. Persistence is session // only — the CLI/WebUI should tell the user to use /risk-profile // permissive if they want this to survive restart. ApprovalElevate // ApprovalAllowFolderSession is the filesystem-tier-B outcome: // approve this invocation AND add the prompt's target folder to // the agent's session-allowed folder list (in-memory). Subsequent // accesses under that folder skip the prompt for the rest of the // session. The folder is conveyed via the approval request's // `folder` extra so the caller knows which path to record. ApprovalAllowFolderSession // ApprovalAlwaysAsk approves this invocation AND persists the // command as an "ask" rule in Config.CommandPolicies so future // matching commands always force an interactive prompt, even in // permissive mode or when the classifier says SAFE. SP-123-2b. ApprovalAlwaysAsk )
func ApprovalDecisionFromString ¶
func ApprovalDecisionFromString(s string) ApprovalDecision
ApprovalDecisionFromString parses a wire-format decision string back into the typed enum. Unknown values resolve to ApprovalDeny for safety.
func (ApprovalDecision) Approved ¶
func (d ApprovalDecision) Approved() bool
Approved reports whether the operation should proceed (any non-Deny decision approves the current invocation).
func (ApprovalDecision) String ¶
func (d ApprovalDecision) String() string
String returns a stable lowercase identifier for the decision, used in event payloads and tests.
type ApprovalKind ¶
type ApprovalKind int
ApprovalKind distinguishes between the two approval flows that share this manager. The kind determines the event type published, the request ID prefix, and the default response used on timeout / nil event-bus.
const ( // ApprovalKindTool is used for tool execution approval (shell_command, // write_file, git ops, etc.). Timeout or nil event-bus rejects for safety. ApprovalKindTool ApprovalKind = iota // ApprovalKindPrompt is used for file content security prompts (API keys, // passwords found in files). Timeout or nil event-bus returns DefaultResponse. ApprovalKindPrompt )
type ApprovalManager ¶
type ApprovalManager struct {
// contains filtered or unexported fields
}
ApprovalManager coordinates security approval requests between the agent and the webui. It subsumes the former SecurityApprovalManager (tool approvals) and SecurityPromptManager (file security prompts) into a single unified manager, eliminating duplicated infrastructure.
The manager is safe for concurrent use. Requests block until a response is received from the webui, a timeout elapses, or the event bus is nil.
func GetGlobalApprovalManager
deprecated
func GetGlobalApprovalManager() *ApprovalManager
GetGlobalApprovalManager returns the global singleton.
Deprecated: use dependency injection via Agent.InjectWebUIManagers instead.
func NewApprovalManager ¶
func NewApprovalManager() *ApprovalManager
NewApprovalManager creates a new ApprovalManager with the default timeout.
func (*ApprovalManager) RequestApproval ¶
func (am *ApprovalManager) RequestApproval(eventBus *events.EventBus, req ApprovalRequest) bool
RequestApproval publishes a security approval/prompt event to the event bus and blocks until the webui responds, a timeout elapses, or the event bus is nil.
For ToolKind: returns true only if explicitly approved; false on rejection, timeout, or nil event-bus. For PromptKind: returns the user response, or DefaultResponse on timeout / nil event-bus.
Callers that need the richer 4-option outcome (ApproveAlways / Elevate) should call RequestApprovalDecision; this wrapper collapses to bool.
func (*ApprovalManager) RequestApprovalDecision ¶
func (am *ApprovalManager) RequestApprovalDecision(eventBus *events.EventBus, req ApprovalRequest) ApprovalDecision
RequestApprovalDecision is the same as RequestApproval but returns the full ApprovalDecision so the caller can distinguish ApproveOnce from ApproveAlways and Elevate. The 4-option UI is currently only wired for shell_command Gate 1/2 callers; everyone else collapses through the bool wrapper above.
func (*ApprovalManager) RequestApprovalDecisionWithOutcome ¶ added in v0.16.7
func (am *ApprovalManager) RequestApprovalDecisionWithOutcome(eventBus *events.EventBus, req ApprovalRequest) (ApprovalDecision, ApprovalOutcome)
RequestApprovalDecisionWithOutcome is the same as RequestApprovalDecision but also returns an ApprovalOutcome so the caller can tell whether the user actually answered (Responded) or the request fell back to its safe default via timeout / missing channel. Callers that have an alternate approval surface (e.g. a terminal prompt) use this to avoid treating an unanswered browser dialog as a deliberate deny.
func (*ApprovalManager) RequestPrompt ¶
func (am *ApprovalManager) RequestPrompt(eventBus *events.EventBus, userID, prompt string, defaultResponse bool, extras map[string]string) bool
RequestPrompt is a convenience wrapper for ApprovalKindPrompt requests. It preserves the original SecurityPromptManager.RequestPrompt signature.
func (*ApprovalManager) RequestToolApproval ¶
func (am *ApprovalManager) RequestToolApproval(eventBus *events.EventBus, clientID, userID, toolName, riskLevel, reasoning string, extras map[string]string) bool
RequestToolApproval is a convenience wrapper for ApprovalKindTool requests. It preserves the original SecurityApprovalManager.RequestApproval signature.
func (*ApprovalManager) RequestToolApprovalDecision ¶
func (am *ApprovalManager) RequestToolApprovalDecision(eventBus *events.EventBus, clientID, userID, toolName, riskLevel, reasoning string, extras map[string]string) ApprovalDecision
RequestToolApprovalDecision is the 4-option variant: it returns the full ApprovalDecision so callers can react to "always approve" and "elevate" choices. Wire payload is the same SecurityApprovalRequest; the WebUI dialog opts into the extra buttons via the matching response schema (action field).
func (*ApprovalManager) RequestToolApprovalDecisionWithOutcome ¶ added in v0.16.7
func (am *ApprovalManager) RequestToolApprovalDecisionWithOutcome(eventBus *events.EventBus, clientID, userID, toolName, riskLevel, reasoning string, extras map[string]string) (ApprovalDecision, ApprovalOutcome)
RequestToolApprovalDecisionWithOutcome is the outcome-aware variant of RequestToolApprovalDecision (the 4-option Gate 2 path).
func (*ApprovalManager) RequestToolApprovalWithOutcome ¶ added in v0.16.7
func (am *ApprovalManager) RequestToolApprovalWithOutcome(eventBus *events.EventBus, clientID, userID, toolName, riskLevel, reasoning string, extras map[string]string) (bool, ApprovalOutcome)
RequestToolApprovalWithOutcome is the outcome-aware variant of RequestToolApproval: it returns whether the operation was approved AND how the request resolved (Responded / TimedOut / NoChannel), so a caller with a terminal fallback can avoid treating an unanswered browser dialog as a deny.
func (*ApprovalManager) RespondToApproval ¶
func (am *ApprovalManager) RespondToApproval(requestID string, response bool) bool
RespondToApproval resolves a pending request with a boolean (legacy path: collapses to ApprovalApproveOnce or ApprovalDeny). Returns true if the request existed and was responded to.
func (*ApprovalManager) RespondToApprovalDecision ¶
func (am *ApprovalManager) RespondToApprovalDecision(requestID string, decision ApprovalDecision) bool
RespondToApprovalDecision resolves a pending request with the full 4-option decision. The WebUI handler uses this to forward the user's "always approve" or "elevate" choice; the bool wrapper RespondToApproval stays for any caller (CLI bridge, tests) that only knows yes/no.
func (*ApprovalManager) RespondToPrompt ¶
func (am *ApprovalManager) RespondToPrompt(requestID string, response bool) bool
RespondToPrompt is a backward-compatible alias for RespondToApproval.
func (*ApprovalManager) SetApprovalTimeout ¶
func (am *ApprovalManager) SetApprovalTimeout(d time.Duration)
SetApprovalTimeout is a backward-compatible alias for SetTimeout.
func (*ApprovalManager) SetPromptTimeout ¶
func (am *ApprovalManager) SetPromptTimeout(d time.Duration)
SetPromptTimeout is a backward-compatible alias for SetTimeout.
func (*ApprovalManager) SetTimeout ¶
func (am *ApprovalManager) SetTimeout(d time.Duration)
SetTimeout sets the maximum duration requests will block. A zero or negative value resets to the default.
type ApprovalOutcome ¶ added in v0.16.7
type ApprovalOutcome int
ApprovalOutcome reports HOW an approval request resolved, independent of the decision itself. It lets callers distinguish a deliberate user "deny" from a dialog that was never answered — so an unattended browser tab can fall back to the terminal prompt instead of dead-ending on a 5-minute timeout that silently denies.
const ( // ApprovalOutcomeResponded — the user (browser) actually answered. // The accompanying decision is authoritative; honor it. ApprovalOutcomeResponded ApprovalOutcome = iota // ApprovalOutcomeTimedOut — no answer arrived within the timeout // window. The decision is the safe default; callers may retry on // another surface. ApprovalOutcomeTimedOut // ApprovalOutcomeNoChannel — the event bus was nil or the response // channel closed without a reply (e.g. the browser disconnected). // The decision is the safe default; callers may retry on another surface. ApprovalOutcomeNoChannel )
type ApprovalRequest ¶
type ApprovalRequest struct {
Kind ApprovalKind
// On timeout / nil event-bus: PromptKind returns DefaultResponse;
// ToolKind always returns false (reject for safety).
DefaultResponse bool
// Tool kind fields
ToolName string
RiskLevel string
Reasoning string
ClientID string
UserID string // User ID for multi-tenant isolation
// Prompt kind fields
Prompt string
// Shared extras forwarded to the event payload.
Extras map[string]string
}
ApprovalRequest captures all parameters for a single approval request.
type DetectedSecret ¶
type DetectedSecret struct {
Type string // e.g. "Env Var Value", "Bearer Token", "API Key"
Snippet string // Matched text, truncated to ~40 chars for display
Line int // 1-based line number where found (0 if not line-scannable)
}
DetectedSecret describes a single secret found in tool output.
type ElevationGate ¶
type ElevationGate struct {
// contains filtered or unexported fields
}
ElevationGate manages user elevation decisions for secret detection. It maintains two session-scoped vaults:
- vault: per-secret decisions keyed by type + first 8 chars of snippet
- sourceAllow: whole-source allowlist (e.g. "read_file: /tmp/example.html")
The user is prompted only once per unique secret pattern OR once per source (whichever they choose).
SAFETY: A single Gate instance is used from one agent goroutine (sequential tool execution driven by seed core.ToolRegistry via pkg/agent/processQueryWithSeed), so no mutex is required. If parallel tool execution is ever introduced, this type must be protected with a sync.Mutex or sync.RWMutex.
func NewElevationGate ¶
func NewElevationGate(prompter SecretPrompter) *ElevationGate
NewElevationGate creates a gate that consults prompter for new detections. Pass nil to default to SecretRedact for uncached secrets (subagent usage).
func (*ElevationGate) Evaluate ¶
func (g *ElevationGate) Evaluate(secrets []DetectedSecret, source string) (SecretAction, error)
Evaluate checks detected secrets against the session vault, prompts for any new ones, and returns the aggregated action to take.
Returns SecretAllow when no secrets were found OR the source has been whitelisted; otherwise the strictest action (any SecretBlock wins, then any SecretRedact, then SecretAllow).
func (*ElevationGate) IsSourceAllowed ¶
func (g *ElevationGate) IsSourceAllowed(source string) bool
IsSourceAllowed reports whether the user has whitelisted this source for the rest of the session. Exported for use by callers that want to skip detection entirely on whitelisted sources.
func (*ElevationGate) SetDefault ¶
func (g *ElevationGate) SetDefault(action SecretAction, envVarName string)
SetDefault pre-seeds the vault for known patterns. Pass envVarName="" for a global catch-all, or e.g. "CommitSecret" to match vault keys of that type.
type OutputRedactor ¶
type OutputRedactor struct {
// contains filtered or unexported fields
}
OutputRedactor scans tool output for secrets using two strategies:
- Environment value matching — literal secret values from env vars
- Pattern matching — regex-based detection of common credential formats
func NewOutputRedactor ¶
func NewOutputRedactor() *OutputRedactor
NewOutputRedactor constructs an OutputRedactor by scanning os.Environ() for env vars whose names suggest they hold credentials (per credentials.IsSensitiveEnvName).
func (*OutputRedactor) RedactFileContent ¶
func (r *OutputRedactor) RedactFileContent(content string, filePath string) RedactionResult
RedactFileContent is a convenience wrapper equivalent to RedactToolOutput but with toolName set to "read_file" and filePath attached for context.
func (*OutputRedactor) RedactToolOutput ¶
func (r *OutputRedactor) RedactToolOutput(output string, toolName string, toolArgs map[string]interface{}) RedactionResult
RedactToolOutput scans tool output for secrets, returning a redacted version and any secrets detected. The toolName and toolArgs are logged but do not change redaction behaviour per tool; they are reserved for future context-aware filtering.
type PermissionChecker ¶
type PermissionChecker struct {
// contains filtered or unexported fields
}
PermissionChecker provides utilities to check file and directory permissions for security-sensitive files in the sprout configuration directory.
func NewPermissionChecker ¶
func NewPermissionChecker(configDir string) *PermissionChecker
NewPermissionChecker creates a new PermissionChecker for the given config directory.
func (*PermissionChecker) CheckAllSecurityFiles ¶
func (pc *PermissionChecker) CheckAllSecurityFiles() []string
CheckAllSecurityFiles checks all security-sensitive files in the config directory. Returns a list of warning messages for files with insecure permissions.
func (*PermissionChecker) CheckConfigDirPermissions ¶
func (pc *PermissionChecker) CheckConfigDirPermissions() string
CheckConfigDirPermissions checks that the config directory has secure permissions (0700). Returns a warning message if permissions are too open.
func (*PermissionChecker) CheckFilePermissions ¶
func (pc *PermissionChecker) CheckFilePermissions(filePath string) string
CheckFilePermissions checks that a file has secure permissions (0600). Returns a warning message if permissions are too open.
func (*PermissionChecker) FixPermissions ¶
func (pc *PermissionChecker) FixPermissions() []error
FixPermissions attempts to fix insecure permissions on security-sensitive files. Returns a list of errors encountered.
type RedactionResult ¶
type RedactionResult struct {
Content string // The (potentially) redacted output
Secrets []DetectedSecret // What was found (if any)
}
RedactionResult holds the output after redaction and any secrets detected.
type SecretAction ¶
type SecretAction int
SecretAction represents the user's decision for a detected secret.
const ( SecretRedact SecretAction = iota // Replace secret with [REDACTED] and continue SecretAllow // Pass through as-is (just this batch) SecretBlock // Stop the operation entirely SecretAllowSource // Pass through as-is AND whitelist the source for the rest of the session )
type SecretPrompter ¶
type SecretPrompter interface {
// PromptSecretAction presents detected secrets to the user and returns
// their decision. Returns error if prompting fails (e.g., non-interactive).
PromptSecretAction(secrets []DetectedSecret, source string) (SecretAction, error)
}
SecretPrompter is implemented by the agent layer to ask the user what to do when secrets are detected. Defined here (in security) to avoid circular imports with the agent package.