{"version":3,"file":"skill-guard.mjs","names":[],"sources":["../../../src/lib/skill-guard.ts"],"sourcesContent":["/**\n * Skill Security Guard — static analysis for agent-created skills.\n *\n * Scans SKILL.md content for dangerous patterns before allowing the agent\n * to persist it. Inspired by Hermes Agent's skills_guard.py (~100 regex\n * patterns covering exfiltration, prompt injection, destructive commands,\n * persistence mechanisms, obfuscation, and supply chain attacks).\n *\n * We adapt this for a crypto DeFi context: the agent should be able to\n * write instructional markdown about trading strategies, but NOT be able\n * to embed code that exfiltrates keys, modifies system prompts, or\n * escalates its own permissions.\n *\n * Three trust levels:\n *   builtin  — static skills shipped with OpenClawnch (always allowed)\n *   learned  — skills the agent created from experience (scanned)\n *   imported — skills from external sources (strictest scanning)\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface SkillScanResult {\n  safe: boolean;\n  findings: SkillFinding[];\n  trustLevel: 'builtin' | 'learned' | 'imported';\n}\n\nexport interface SkillFinding {\n  severity: 'critical' | 'high' | 'medium' | 'info';\n  category: string;\n  pattern: string;\n  match: string;\n  line: number;\n  description: string;\n}\n\n// ─── Pattern Categories ──────────────────────────────────────────────────\n\ninterface ScanPattern {\n  category: string;\n  severity: 'critical' | 'high' | 'medium' | 'info';\n  regex: RegExp;\n  description: string;\n}\n\nconst SCAN_PATTERNS: ScanPattern[] = [\n  // ── Prompt Injection ───────────────────────────────────────────────\n  {\n    category: 'prompt_injection',\n    severity: 'critical',\n    regex: /ignore\\s+(all\\s+)?previous\\s+instructions/gi,\n    description: 'Attempts to override system prompt instructions',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'critical',\n    regex: /you\\s+are\\s+now\\s+(a|an|the)\\s+/gi,\n    description: 'Attempts to redefine agent identity',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'critical',\n    regex: /forget\\s+(everything|all|your)\\s+(you|instructions|rules)/gi,\n    description: 'Attempts to clear agent context',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'critical',\n    regex: /\\bsystem\\s*:\\s*you\\s+(must|should|will|are)\\b/gi,\n    description: 'Fake system message injection',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'high',\n    regex: /<\\/?system[-_]?(message|prompt|instruction|override)>/gi,\n    description: 'XML-style system prompt injection tags',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'high',\n    regex: /\\bdo\\s+not\\s+(tell|reveal|mention|show)\\s+(the\\s+)?user\\b/gi,\n    description: 'Attempts to hide information from user',\n  },\n  {\n    category: 'prompt_injection',\n    severity: 'high',\n    regex: /\\bact\\s+as\\s+if\\s+(you|the)\\s+(are|is|have)\\b/gi,\n    description: 'Social engineering via role assumption',\n  },\n\n  // ── Exfiltration ───────────────────────────────────────────────────\n  {\n    category: 'exfiltration',\n    severity: 'critical',\n    regex: /\\bfetch\\s*\\(\\s*['\"`]https?:\\/\\//gi,\n    description: 'Embedded fetch call to external URL',\n  },\n  {\n    category: 'exfiltration',\n    severity: 'critical',\n    regex: /\\b(curl|wget|nc|ncat|netcat)\\s+/gi,\n    description: 'Network exfiltration command',\n  },\n  {\n    category: 'exfiltration',\n    severity: 'critical',\n    regex: /process\\.env\\.(PRIVATE_KEY|BANKR_API_KEY|CLAWNCHER_PRIVATE_KEY)/gi,\n    description: 'Direct secret access in skill content',\n  },\n  {\n    category: 'exfiltration',\n    severity: 'high',\n    regex: /\\bwebhook\\.site\\b|\\brequestbin\\b|\\bpipedream\\.net\\b/gi,\n    description: 'Known data exfiltration endpoints',\n  },\n  {\n    category: 'exfiltration',\n    severity: 'high',\n    regex: /\\bsend\\s+(the|your|all)\\s+(private|secret|api)\\s*(key|token)/gi,\n    description: 'Instruction to exfiltrate credentials',\n  },\n\n  // ── Destructive Operations ─────────────────────────────────────────\n  {\n    category: 'destructive',\n    severity: 'critical',\n    regex: /\\brm\\s+-rf\\s+[\\/~]/gi,\n    description: 'Recursive file deletion command',\n  },\n  {\n    category: 'destructive',\n    severity: 'critical',\n    regex: /\\b(chmod|chown)\\s+.*\\s+(\\/|~)/gi,\n    description: 'File permission modification on system paths',\n  },\n  {\n    category: 'destructive',\n    severity: 'high',\n    regex: /\\b(drop|truncate|delete\\s+from)\\s+\\w+/gi,\n    description: 'Database destructive operation',\n  },\n\n  // ── Privilege Escalation ───────────────────────────────────────────\n  {\n    category: 'privilege_escalation',\n    severity: 'critical',\n    regex: /\\bsudo\\s+/gi,\n    description: 'Privilege escalation via sudo',\n  },\n  {\n    category: 'privilege_escalation',\n    severity: 'critical',\n    regex: /\\bALLOW_PRIVATE_KEY_MODE\\s*=\\s*true/gi,\n    description: 'Attempts to enable private key mode',\n  },\n  {\n    category: 'privilege_escalation',\n    severity: 'high',\n    regex: /\\b(danger\\s*mode|disable\\s+safety|skip\\s+confirmation)/gi,\n    description: 'Attempts to weaken safety controls',\n  },\n  {\n    category: 'privilege_escalation',\n    severity: 'high',\n    regex: /\\bOPENCLAWNCH_ALLOWLIST_MODE\\s*=\\s*(off|warn)/gi,\n    description: 'Attempts to weaken endpoint allowlist',\n  },\n\n  // ── Obfuscation ────────────────────────────────────────────────────\n  {\n    category: 'obfuscation',\n    severity: 'high',\n    regex: /\\b(eval|Function)\\s*\\(/gi,\n    description: 'Dynamic code evaluation',\n  },\n  {\n    category: 'obfuscation',\n    severity: 'high',\n    regex: /\\batob\\s*\\(|btoa\\s*\\(/gi,\n    description: 'Base64 encoding/decoding (potential obfuscation)',\n  },\n  {\n    category: 'obfuscation',\n    severity: 'medium',\n    regex: /\\\\x[0-9a-fA-F]{2}(?:\\\\x[0-9a-fA-F]{2}){3,}/g,\n    description: 'Hex-encoded string (potential obfuscation)',\n  },\n  {\n    category: 'obfuscation',\n    severity: 'high',\n    regex: /[\\u200B-\\u200F\\u202A-\\u202E\\uFEFF]/g,\n    description: 'Invisible Unicode characters (zero-width, bidirectional overrides)',\n  },\n\n  // ── Crypto-Specific Dangers ────────────────────────────────────────\n  {\n    category: 'crypto_danger',\n    severity: 'critical',\n    regex: /\\b(approve|setApprovalForAll)\\s*\\(\\s*['\"`]?0x/gi,\n    description: 'Embedded token approval to hardcoded address',\n  },\n  {\n    category: 'crypto_danger',\n    severity: 'critical',\n    regex: /\\bmax\\s*uint256\\b|\\btype\\(uint256\\)\\.max\\b|ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/gi,\n    description: 'Unlimited token approval (max uint256)',\n  },\n  {\n    category: 'crypto_danger',\n    severity: 'high',\n    regex: /\\b(always|automatically)\\s+(approve|swap|transfer|bridge|send)\\b/gi,\n    description: 'Instruction to bypass confirmation for financial operations',\n  },\n  {\n    category: 'crypto_danger',\n    severity: 'high',\n    regex: /\\bslippage\\s*[=:]\\s*(100|[5-9]\\d)\\s*%/gi,\n    description: 'Extremely high slippage tolerance (50%+)',\n  },\n  {\n    category: 'crypto_danger',\n    severity: 'medium',\n    regex: /\\b(honeypot|rug\\s*pull|drain|scam)\\s+(token|contract|this)/gi,\n    description: 'References to known scam patterns in instructions',\n  },\n\n  // ── Persistence / Backdoor ─────────────────────────────────────────\n  {\n    category: 'persistence',\n    severity: 'critical',\n    regex: /\\bcrontab\\b|\\bcron\\s+/gi,\n    description: 'Cron job installation (persistence mechanism)',\n  },\n  {\n    category: 'persistence',\n    severity: 'critical',\n    regex: /\\b(systemd|launchd|\\.plist|\\.service)\\b/gi,\n    description: 'System service installation (persistence mechanism)',\n  },\n  {\n    category: 'persistence',\n    severity: 'high',\n    regex: /\\b(\\.bashrc|\\.zshrc|\\.profile|\\.bash_profile)\\b/gi,\n    description: 'Shell profile modification',\n  },\n\n  // ── Self-Modification ──────────────────────────────────────────────\n  {\n    category: 'self_modification',\n    severity: 'critical',\n    regex: /\\bwriteFile(Sync)?\\s*\\(\\s*['\"`].*index\\.ts/gi,\n    description: 'Attempts to modify plugin entry point',\n  },\n  {\n    category: 'self_modification',\n    severity: 'critical',\n    regex: /\\bwriteFile(Sync)?\\s*\\(\\s*['\"`].*endpoint-allowlist/gi,\n    description: 'Attempts to modify the endpoint allowlist',\n  },\n  {\n    category: 'self_modification',\n    severity: 'high',\n    regex: /\\bwriteFile(Sync)?\\s*\\(\\s*['\"`].*credential-vault/gi,\n    description: 'Attempts to modify the credential vault',\n  },\n  {\n    category: 'self_modification',\n    severity: 'high',\n    regex: /\\brequire\\s*\\(\\s*['\"`]child_process/gi,\n    description: 'Attempts to import child_process module',\n  },\n\n  // ── Supply Chain ───────────────────────────────────────────────────\n  {\n    category: 'supply_chain',\n    severity: 'high',\n    regex: /\\bnpm\\s+(install|i)\\s+/gi,\n    description: 'Package installation command in skill',\n  },\n  {\n    category: 'supply_chain',\n    severity: 'high',\n    regex: /\\bpip\\s+install\\b/gi,\n    description: 'Python package installation in skill',\n  },\n];\n\n// ─── Scanner ─────────────────────────────────────────────────────────────\n\n/**\n * Scan skill content for security issues.\n *\n * @param content - The full skill markdown content\n * @param trustLevel - How much we trust the source\n * @returns Scan result with findings and safe/unsafe determination\n */\nexport function scanSkillContent(\n  content: string,\n  trustLevel: 'builtin' | 'learned' | 'imported' = 'learned',\n): SkillScanResult {\n  // Builtin skills are always trusted\n  if (trustLevel === 'builtin') {\n    return { safe: true, findings: [], trustLevel };\n  }\n\n  const findings: SkillFinding[] = [];\n  const lines = content.split('\\n');\n\n  for (const pattern of SCAN_PATTERNS) {\n    pattern.regex.lastIndex = 0;\n\n    for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n      const line = lines[lineIdx] ?? '';\n      let match: RegExpExecArray | null;\n      pattern.regex.lastIndex = 0;\n\n      while ((match = pattern.regex.exec(line)) !== null) {\n        findings.push({\n          severity: pattern.severity,\n          category: pattern.category,\n          pattern: pattern.regex.source.slice(0, 60),\n          match: match[0].slice(0, 100),\n          line: lineIdx + 1,\n          description: pattern.description,\n        });\n      }\n    }\n  }\n\n  // Determine if safe based on trust level and findings\n  const hasCritical = findings.some(f => f.severity === 'critical');\n  const hasHigh = findings.some(f => f.severity === 'high');\n\n  let safe: boolean;\n  if (trustLevel === 'imported') {\n    // Imported skills: any finding blocks\n    safe = findings.length === 0;\n  } else {\n    // Learned skills: critical or high blocks\n    safe = !hasCritical && !hasHigh;\n  }\n\n  return { safe, findings, trustLevel };\n}\n\n/**\n * Format scan findings as a human-readable report.\n */\nexport function formatScanReport(result: SkillScanResult): string {\n  if (result.safe && result.findings.length === 0) {\n    return 'Skill scan: CLEAN — no issues found.';\n  }\n\n  const lines = [\n    result.safe\n      ? 'Skill scan: PASSED with informational findings.'\n      : 'Skill scan: BLOCKED — security issues detected.',\n    '',\n  ];\n\n  const grouped = new Map<string, SkillFinding[]>();\n  for (const f of result.findings) {\n    const key = `${f.severity}:${f.category}`;\n    if (!grouped.has(key)) grouped.set(key, []);\n    grouped.get(key)!.push(f);\n  }\n\n  // Sort by severity\n  const severityOrder = { critical: 0, high: 1, medium: 2, info: 3 };\n  const sortedKeys = [...grouped.keys()].sort((a, b) => {\n    const sevA = (a.split(':')[0] ?? '') as keyof typeof severityOrder;\n    const sevB = (b.split(':')[0] ?? '') as keyof typeof severityOrder;\n    return (severityOrder[sevA] ?? 4) - (severityOrder[sevB] ?? 4);\n  });\n\n  for (const key of sortedKeys) {\n    const items = grouped.get(key)!;\n    const [severity = '', category] = key.split(':');\n    const icon = severity === 'critical' ? '[!!]' : severity === 'high' ? '[!]' : '[i]';\n    lines.push(`${icon} [${severity.toUpperCase()}] ${category} (${items.length} finding${items.length > 1 ? 's' : ''}):`);\n    for (const f of items.slice(0, 5)) {\n      lines.push(`  Line ${f.line}: ${f.description}`);\n      lines.push(`    Match: \"${f.match}\"`);\n    }\n    if (items.length > 5) {\n      lines.push(`  ... and ${items.length - 5} more`);\n    }\n  }\n\n  return lines.join('\\n');\n}\n\n/**\n * Validate skill frontmatter structure.\n * Returns an array of validation errors (empty = valid).\n */\nexport function validateSkillFrontmatter(frontmatter: Record<string, unknown>): string[] {\n  const errors: string[] = [];\n\n  if (!frontmatter.name || typeof frontmatter.name !== 'string') {\n    errors.push('Missing or invalid \"name\" field (required, must be a string)');\n  } else if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(frontmatter.name as string)) {\n    errors.push('Invalid \"name\" format — must be lowercase kebab-case (e.g., \"my-skill\")');\n  }\n\n  if (!frontmatter.description || typeof frontmatter.description !== 'string') {\n    errors.push('Missing or invalid \"description\" field (required, must be a string)');\n  }\n\n  if (typeof frontmatter.name === 'string' && (frontmatter.name as string).length > 60) {\n    errors.push('Skill name too long (max 60 characters)');\n  }\n\n  if (typeof frontmatter.description === 'string' && (frontmatter.description as string).length > 500) {\n    errors.push('Skill description too long (max 500 characters)');\n  }\n\n  return errors;\n}\n"],"mappings":";AA6CA,MAAM,gBAA+B;CAEnC;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CAGD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CAGD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CAGD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CAGD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CACD;EACE,UAAU;EACV,UAAU;EACV,OAAO;EACP,aAAa;EACd;CAGD;EACE,UAAU;EACV,UAAU;EACV,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"}