Documentation
¶
Overview ¶
Package security — encoding-aware scan layer.
Plain regex on input bytes misses encoded bypasses. An agent (hallucinating or adversarial) can write:
echo cm0gLXJmIC8K | base64 -d | sh
where `cm0gLXJmIC8K` decodes to `rm -rf /`. The original Scan pattern set only matches plain-text — without this file, the dangerous command slips through every deny pattern.
Approach: decode every plausible-looking base64 / hex run in the input and feed each decoded fragment back through the same Scan pipeline. If a decoded fragment matches a deny pattern, the finding gets reported as if it had been written in plain text. Original Scan still runs first so legitimate plain-text commands behave exactly as before.
Defenses against the obvious counter:
- Bare base64-looking strings (JWTs, image data URIs, hashes) decode to garbage that won't match a command pattern. False positives only fire when the decoded text actually contains a dangerous command.
- We don't decode unboundedly — runs shorter than 12 chars skip (too noisy), longer than 4KB skip (almost certainly not a command).
- "decode-and-exec" SHAPES (base64 -d | sh, eval $(...|base64), xxd -r -p | bash) get a dedicated high-severity pattern regardless of decode success — those constructs have no legitimate purpose in agent traffic.
Package security's pairing.go implements DM pairing — sender identity verification for messaging gateways. Stolen from OpenClaw's src/pairing/.
OpenClaw's fix is dead simple: unknown senders get an 8-char code (no ambiguous 0/O/1/I), owner approves via CLI or web dashboard, sender gets added to per-channel allowFrom whitelist. Storage is JSON files with atomic temp+rename writes under ~/.overkill/pairing/.
Package security — privilege separation (master plan §4.3 / paper #21).
PrivilegeMode toggles whether write-like tool calls are permitted. The reader/planner phase runs in ReaderMode; once the user confirms the plan the agent flips to WriterMode. This keeps an over-eager planner from silently mutating the repo.
Enforcement lives at the tool layer: the agent's pre-tool hook asks PrivilegeGate.Allow(toolName, input) before dispatching.
Package security provides shared security primitives used across internal packages — path containment, credential masking, and prompt-injection detection.
Index ¶
- Variables
- func IsWriteLikeTool(name string, raw json.RawMessage) bool
- func SafePath(dir, name string) (string, error)
- type CommandScanner
- type CommandScannerOption
- func WithExtraDenyPatterns(patterns []string) CommandScannerOption
- func WithForbiddenPaths(paths []string) CommandScannerOption
- func WithMaxCommandLen(n int) CommandScannerOption
- func WithPermissionManager(pm *PermissionManager) CommandScannerOption
- func WithProjectPath(path string) CommandScannerOption
- func WithSandbox(enabled bool) CommandScannerOption
- type Finding
- type InjectionScanner
- type KnownSender
- type Ledger
- type LedgerEntry
- type PairingConfig
- type PairingMode
- type PairingRequest
- type PairingStore
- func (ps *PairingStore) ApproveCode(channel, code string) (*KnownSender, error)
- func (ps *PairingStore) IsKnown(channel, senderID, accountID string) bool
- func (ps *PairingStore) IssueChallenge(channel, senderID, accountID string) (*PairingRequest, error)
- func (ps *PairingStore) ListKnown(channel string) ([]KnownSender, error)
- func (ps *PairingStore) ListPending() ([]PairingRequest, error)
- func (ps *PairingStore) RemoveSender(channel, senderID, accountID string) error
- type PermissionAction
- type PermissionDecision
- type PermissionManager
- func (pm *PermissionManager) Allow(decision PermissionDecision)
- func (pm *PermissionManager) Check(pattern, command, projectPath string) PermissionDecision
- func (pm *PermissionManager) ClearProject(projectPath string)
- func (pm *PermissionManager) ClearSession()
- func (pm *PermissionManager) IsAllowed(pattern, command, projectPath string) bool
- type PrivilegeGate
- type PrivilegeMode
- type ScanResult
- type Scanner
- type SecretScanner
- type ThreatLevel
Constants ¶
This section is empty.
Variables ¶
var ErrWriteDenied = errors.New("privilege: write denied — agent is in reader mode")
ErrWriteDenied is returned when a write-like call is blocked by ReaderMode.
Functions ¶
func IsWriteLikeTool ¶
func IsWriteLikeTool(name string, raw json.RawMessage) bool
Reader mode admits explicitly classified observations. Arbitrary command execution and unknown adapters require writer authority: command text cannot establish absence of redirections, interpreters, hooks or remote side effects. This is tool admission, not an OS sandbox for trusted hooks/plugins.
func SafePath ¶
SafePath joins a trusted directory with a caller-supplied name, enforcing that the resulting path stays within the directory.
It rejects:
- Absolute paths (e.g. "/etc/passwd").
- Path traversal via ".." components.
- Symlink-based escapes (by resolving absolute paths and checking the prefix).
Returns the sanitised full path on success.
Types ¶
type CommandScanner ¶
type CommandScanner struct {
// contains filtered or unexported fields
}
func NewCommandScanner ¶
func NewCommandScanner(opts ...CommandScannerOption) *CommandScanner
func (*CommandScanner) Name ¶
func (s *CommandScanner) Name() string
func (*CommandScanner) Scan ¶
func (s *CommandScanner) Scan(input string) (*ScanResult, error)
type CommandScannerOption ¶
type CommandScannerOption func(*CommandScanner)
func WithExtraDenyPatterns ¶
func WithExtraDenyPatterns(patterns []string) CommandScannerOption
WithExtraDenyPatterns appends user-supplied regexes to the scanner's deny list. Each entry is compiled at construction; invalid regexes are silently dropped (logging is left to the caller). All matches produce ThreatHigh blocks under the id "user_deny_<n>".
func WithForbiddenPaths ¶
func WithForbiddenPaths(paths []string) CommandScannerOption
WithForbiddenPaths adds a regex that blocks any command containing any of the supplied path substrings. Empty list is a no-op.
func WithMaxCommandLen ¶
func WithMaxCommandLen(n int) CommandScannerOption
WithMaxCommandLen caps the length of an input passed to Scan. Inputs over the cap are blocked with a ThreatHigh finding. 0 disables.
func WithPermissionManager ¶
func WithPermissionManager(pm *PermissionManager) CommandScannerOption
func WithProjectPath ¶
func WithProjectPath(path string) CommandScannerOption
func WithSandbox ¶
func WithSandbox(enabled bool) CommandScannerOption
WithSandbox injects additional deny patterns that restrict what the agent can do on the host. When enabled:
- No privilege escalation (sudo, su, pkexec)
- No network exfiltration tools (nc, socat beyond localhost)
- No writes outside the project directory
- No systemd/service manipulation
type Finding ¶
type Finding struct {
Type string
Level ThreatLevel
Description string
Match string
Confidence float64
}
func ShellSyntaxFindings ¶
ShellSyntaxFindings statically inspects shell syntax without expanding words or executing anything. It supplements the legacy text patterns (including user patterns); it is not a shell sandbox. Callers accepting commands must reject parse errors. Generic text scanners may ignore them because they also scan prose.
type InjectionScanner ¶
type InjectionScanner struct {
// contains filtered or unexported fields
}
func NewInjectionScanner ¶
func NewInjectionScanner() *InjectionScanner
func (*InjectionScanner) Name ¶
func (s *InjectionScanner) Name() string
func (*InjectionScanner) Scan ¶
func (s *InjectionScanner) Scan(input string) (*ScanResult, error)
type KnownSender ¶
type KnownSender struct {
ID string `json:"id"`
Channel string `json:"channel"`
AddedAt time.Time `json:"addedAt"`
AddedBy string `json:"addedBy"` // "cli" | "web"
}
KnownSender is an approved sender on a channel.
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger appends permission decisions to a per-session JSONL file and serves them back to the TUI for the /permissions overlay.
func NewLedger ¶
NewLedger constructs a session ledger at ~/.overkill/sessions/<id>/permissions.jsonl (or the path given). The directory is created if missing.
func (*Ledger) Append ¶
func (l *Ledger) Append(e LedgerEntry) error
Append writes a new entry to disk and memory.
func (*Ledger) Entries ¶
func (l *Ledger) Entries() []LedgerEntry
Entries returns a snapshot of all entries (oldest first).
func (*Ledger) Filter ¶
func (l *Ledger) Filter(pred func(LedgerEntry) bool) []LedgerEntry
Filter returns entries matching the predicate.
type LedgerEntry ¶
type LedgerEntry struct {
Time time.Time `json:"ts"`
Tool string `json:"tool"`
Args string `json:"args"`
Decision string `json:"decision"` // allow_once | allow_session | allow_global | deny
Risk string `json:"risk,omitempty"`
}
LedgerEntry records a single permission decision for audit/recall.
type PairingConfig ¶
type PairingConfig struct {
Enabled bool `json:"enabled"`
Channels map[string]PairingMode `json:"channels"` // per-channel mode
TTLMinutes int `json:"ttlMinutes"` // override default TTL
MaxPending int `json:"maxPending"` // override max pending
}
PairingConfig is embedded in the main SecurityConfig.
type PairingMode ¶
type PairingMode string
PairingMode controls sender verification behavior per channel.
const ( // PairingOff — all senders are processed (legacy behavior). PairingOff PairingMode = "off" // PairingOn — unknown senders get a pairing challenge. PairingOn PairingMode = "pairing" // PairingOpen — all senders are processed (explicit opt-in to legacy). PairingOpen PairingMode = "open" )
type PairingRequest ¶
type PairingRequest struct {
ID string `json:"id"` // sender ID (phone number, user ID)
Code string `json:"code"` // 8-char one-time code
Channel string `json:"channel"` // "telegram", "discord", etc.
AccountID string `json:"accountId"` // multi-account support
CreatedAt time.Time `json:"createdAt"`
LastSeenAt time.Time `json:"lastSeenAt"`
}
PairingRequest is a pending approval record for one sender.
type PairingStore ¶
type PairingStore struct {
// contains filtered or unexported fields
}
PairingStore manages pending pairing requests and approved senders. Storage: JSON files under ~/.overkill/pairing/ with atomic temp+rename.
func NewPairingStore ¶
func NewPairingStore(dir string) *PairingStore
NewPairingStore wires the store to a directory. Creates the directory lazily on first write.
func (*PairingStore) ApproveCode ¶
func (ps *PairingStore) ApproveCode(channel, code string) (*KnownSender, error)
ApproveCode looks up a pending request by code (case-insensitive match), removes it from pending, and adds the sender to the allowFrom whitelist.
func (*PairingStore) IsKnown ¶
func (ps *PairingStore) IsKnown(channel, senderID, accountID string) bool
IsKnown reports whether a sender has been approved for a channel.
func (*PairingStore) IssueChallenge ¶
func (ps *PairingStore) IssueChallenge(channel, senderID, accountID string) (*PairingRequest, error)
IssueChallenge creates a new pairing request for an unknown sender. If the sender already has a pending request, bumps LastSeenAt and reuses the code. If max pending is exceeded, the oldest (by LastSeenAt) is pruned.
func (*PairingStore) ListKnown ¶
func (ps *PairingStore) ListKnown(channel string) ([]KnownSender, error)
ListKnown returns all approved senders for a channel.
func (*PairingStore) ListPending ¶
func (ps *PairingStore) ListPending() ([]PairingRequest, error)
ListPending returns all pending pairing requests across all channels.
func (*PairingStore) RemoveSender ¶
func (ps *PairingStore) RemoveSender(channel, senderID, accountID string) error
RemoveSender removes a sender from the allowFrom whitelist.
type PermissionAction ¶
type PermissionAction int
const ( ActionDeny PermissionAction = iota ActionAllowOnce ActionAllowProject ActionAllowGlobal )
func (PermissionAction) String ¶
func (a PermissionAction) String() string
type PermissionDecision ¶
type PermissionManager ¶
type PermissionManager struct {
// contains filtered or unexported fields
}
func NewPermissionManager ¶
func NewPermissionManager() *PermissionManager
func (*PermissionManager) Allow ¶
func (pm *PermissionManager) Allow(decision PermissionDecision)
func (*PermissionManager) Check ¶
func (pm *PermissionManager) Check(pattern, command, projectPath string) PermissionDecision
func (*PermissionManager) ClearProject ¶
func (pm *PermissionManager) ClearProject(projectPath string)
func (*PermissionManager) ClearSession ¶
func (pm *PermissionManager) ClearSession()
func (*PermissionManager) IsAllowed ¶
func (pm *PermissionManager) IsAllowed(pattern, command, projectPath string) bool
IsAllowed is a non-consuming predicate. It must NOT consume an AllowOnce grant — that's what Check does. Otherwise a caller asking "are we allowed?" would silently burn the user's one-shot permission.
type PrivilegeGate ¶
type PrivilegeGate struct {
// contains filtered or unexported fields
}
PrivilegeGate gates tool calls based on the current mode. Thread-safe.
func NewPrivilegeGate ¶
func NewPrivilegeGate(start PrivilegeMode) *PrivilegeGate
NewPrivilegeGate starts in writer mode (preserves legacy behavior). Callers flip to reader for plan-only sessions.
func (*PrivilegeGate) Allow ¶
func (g *PrivilegeGate) Allow(toolName string, rawInput json.RawMessage) (bool, string)
Allow reports whether a tool call is permitted under the current mode. Always true in writer mode. Reader mode permits only classified observations; opaque commands/adapters and malformed or mutating actions require writer mode.
func (*PrivilegeGate) Mode ¶
func (g *PrivilegeGate) Mode() PrivilegeMode
Mode returns the current mode.
func (*PrivilegeGate) SetMode ¶
func (g *PrivilegeGate) SetMode(m PrivilegeMode) PrivilegeMode
SetMode switches the mode. Returns the previous mode.
type PrivilegeMode ¶
type PrivilegeMode string
PrivilegeMode is "reader" (read-only) or "writer" (full).
const ( ModeReader PrivilegeMode = "reader" ModeWriter PrivilegeMode = "writer" )
type ScanResult ¶
type ScanResult struct {
Findings []Finding
MaxLevel ThreatLevel
Blocked bool
Sanitized string
}
type SecretScanner ¶
type SecretScanner struct {
// contains filtered or unexported fields
}
func NewSecretScanner ¶
func NewSecretScanner() *SecretScanner
func (*SecretScanner) Name ¶
func (s *SecretScanner) Name() string
func (*SecretScanner) Scan ¶
func (s *SecretScanner) Scan(input string) (*ScanResult, error)
type ThreatLevel ¶
type ThreatLevel int
const ( ThreatNone ThreatLevel = iota ThreatLow ThreatMedium ThreatHigh ThreatCritical )
func (ThreatLevel) String ¶
func (l ThreatLevel) String() string