mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 04:16:06 +00:00
Adds skill_manage — a first-class agent tool for creating, updating, and deleting skills from within a conversation — paired with per-agent skill_evolve that nudges predefined agents to capture reusable workflows. Tool (skill_manage): - create: write skill from SKILL.md content string (auto-grant, dep scan) - patch: find/replace producing new immutable version (advisory-locked) - delete: soft-delete (archive in DB, move to .trash/) - Security guard: 25 regex patterns block shell injection, credential exfil, path traversal, SQL injection, privilege escalation - Ownership enforced: only skill owner can patch/delete (admin bypass) - Content size limit: 100KB; companion file copy: 20MB, symlink-safe (WalkDir) - Enabled: false by default — admin opt-in per agent Learning loop (skill_evolve, predefined agents only): - System prompt: SHOULD/SHOULD NOT guidance for skill creation - Budget nudges: [System] prefix at 70%/90% iteration budget (ephemeral, i18n) - Postscript: once-per-run suggestion with explicit user consent - Config: other_config.skill_evolve + skill_nudge_interval (default 15) Security hardening (pre-existing + new): - CreateSkillManaged: RETURNING id + pg_advisory_xact_lock (atomic upsert) - GetNextVersionLocked: advisory lock for race-safe patch versioning - Ownership checks on HTTP update/delete, gateway update, 4 grant/revoke handlers - copyOtherFiles: filepath.WalkDir for real symlink detection UI: Skill Learning toggle + nudge interval in Agent General Tab i18n: backend (en/vi/zh catalogs) + frontend (en/vi/zh locale files)
103 lines
4.6 KiB
Go
103 lines
4.6 KiB
Go
package skills
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// GuardViolation describes a security pattern match in SKILL.md content.
|
|
type GuardViolation struct {
|
|
Pattern string
|
|
Reason string
|
|
Line int
|
|
}
|
|
|
|
// guardRule pairs a compiled regexp with a human-readable rejection reason.
|
|
type guardRule struct {
|
|
re *regexp.Regexp
|
|
reason string
|
|
}
|
|
|
|
// skillGuardRules contains high-signal security patterns for SKILL.md content.
|
|
// Focus: shell injection, destructive ops, obfuscated payloads, credential exfil,
|
|
// path traversal, SQL injection, privilege escalation.
|
|
//
|
|
// Not exhaustive — GoClaw's exec tool has its own runtime deny-list.
|
|
// This scanner runs BEFORE the file is written, blocking poisoned skills at creation time.
|
|
var skillGuardRules = []guardRule{
|
|
// --- Destructive shell operations ---
|
|
{regexp.MustCompile(`(?i)rm\s+-rf\s+[/~]`), "destructive rm -rf on root/home"},
|
|
{regexp.MustCompile(`:\s*\|\s*:\s*&`), "fork bomb (self-pipe-to-background pattern)"},
|
|
{regexp.MustCompile(`(?i)\bdd\b.+\bof=/dev/`), "disk overwrite via dd"},
|
|
{regexp.MustCompile(`(?i)\bmkfs\b`), "filesystem format command"},
|
|
{regexp.MustCompile(`(?i)\bshred\s`), "file shredding command"},
|
|
|
|
// --- Code injection / obfuscation ---
|
|
{regexp.MustCompile(`(?i)base64\s+(--decode|-d)\s*\|`), "base64 decode piped to shell"},
|
|
{regexp.MustCompile(`(?i)eval\s*\(\s*base64`), "eval of base64-encoded payload"},
|
|
{regexp.MustCompile(`(?i)eval\s*\$\s*\(`), "eval of subshell output"},
|
|
{regexp.MustCompile(`(?i)curl\s+\S+\s*\|\s*(ba)?sh`), "curl pipe to shell"},
|
|
{regexp.MustCompile(`(?i)wget\s+\S+\s*\|\s*(ba)?sh`), "wget pipe to shell"},
|
|
{regexp.MustCompile(`(?i)python[23]?\s+-c\s+.{0,40}exec\s*\(`), "python exec injection"},
|
|
{regexp.MustCompile(`(?i)__import__\s*\(\s*['"]os['"]`), "Python os import obfuscation"},
|
|
|
|
// --- Credential / data exfiltration ---
|
|
{regexp.MustCompile(`(?i)/etc/passwd\b`), "system password file access"},
|
|
{regexp.MustCompile(`(?i)/etc/shadow\b`), "shadow password file access"},
|
|
{regexp.MustCompile(`(?i)\.ssh/id_rsa`), "private SSH key access"},
|
|
{regexp.MustCompile(`(?i)AWS_SECRET_ACCESS_KEY`), "AWS credential reference"},
|
|
{regexp.MustCompile(`(?i)GOCLAW_DB_URL\b`), "GoClaw database credential"},
|
|
{regexp.MustCompile(`(?i)(curl|wget)\s+\S+.*\$\{?(HOME|USER|PASS|KEY|SECRET|TOKEN)`), "env var exfiltration via HTTP"},
|
|
|
|
// --- Path traversal ---
|
|
{regexp.MustCompile(`\.\./\.\./\.\./`), "deep path traversal (../../..)"},
|
|
|
|
// --- SQL injection ---
|
|
{regexp.MustCompile(`(?i)\bDROP\s+TABLE\b`), "SQL DROP TABLE"},
|
|
{regexp.MustCompile(`(?i)\bTRUNCATE\s+TABLE\b`), "SQL TRUNCATE TABLE"},
|
|
{regexp.MustCompile(`(?i)\bDROP\s+DATABASE\b`), "SQL DROP DATABASE"},
|
|
|
|
// --- Privilege escalation ---
|
|
{regexp.MustCompile(`(?i)\bsudo\s`), "sudo usage"},
|
|
{regexp.MustCompile(`(?i)\bchmod\s+[0-7]*[2367]\b`), "world-writable chmod (others write bit set)"},
|
|
{regexp.MustCompile(`(?i)\bchown\s+root\b`), "chown to root"},
|
|
}
|
|
|
|
// GuardSkillContent scans SKILL.md content for security violations before any disk write.
|
|
// Returns (violations, safe). safe=true means no violations found and the content may be written.
|
|
//
|
|
// Scanning is line-by-line: one violation per line (first matching rule wins).
|
|
// Hard-reject on ANY violation — no partial allow.
|
|
func GuardSkillContent(content string) ([]GuardViolation, bool) {
|
|
lines := strings.Split(content, "\n")
|
|
var violations []GuardViolation
|
|
|
|
for lineNum, line := range lines {
|
|
for _, rule := range skillGuardRules {
|
|
if rule.re.MatchString(line) {
|
|
violations = append(violations, GuardViolation{
|
|
Pattern: rule.re.String(),
|
|
Reason: rule.reason,
|
|
Line: lineNum + 1,
|
|
})
|
|
break // one violation per line is sufficient
|
|
}
|
|
}
|
|
}
|
|
|
|
return violations, len(violations) == 0
|
|
}
|
|
|
|
// FormatGuardViolations returns a human-readable rejection message suitable
|
|
// for returning to the LLM as a tool error result.
|
|
func FormatGuardViolations(violations []GuardViolation) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("Skill content rejected by security scanner. Violations:\n")
|
|
for _, v := range violations {
|
|
sb.WriteString(fmt.Sprintf(" - Line %d: %s\n", v.Line, v.Reason))
|
|
}
|
|
sb.WriteString("\nRemove the flagged content and try again.")
|
|
return sb.String()
|
|
}
|