Skill: ntaffffff/skill-security-scan-dxx (version 1.0.0)

--- name: skill-security-scan description: Scan installed OpenClaw skills for potential security risks. Use when you want to check if skills contain dangerous commands, access sensitive paths, or have other security issues. triggers: keywords: - "skill安全" - "扫描skill" - "检查skill" - "skill风险" - "skill安全扫描" - "skill安全检查" - "skill藏毒" conditions: - "需要检查skill安全性" - "担心skill有恶意代码" - "要扫描已安装的skill" --- # Skill Security Scan 扫描已安装的 OpenClaw skill，检测潜在的安全风险。 ## 功能 - 🔍 扫描所有已安装 skill - ⚠️ 检测危险命令（rm -rf /、fork 炸弹等） - 🔒 检查敏感路径访问 - 🌐 检查网络请求安全性 - 📊 生成风险报告 ## 使用方法 ```bash # 运行安全扫描 python3 ~/.openclaw/workspace/skills/skill-security-scan/skill_scan.py ``` ## 检查项 ### 高风险 - `rm -rf /` - 删除根目录 - `rm -rf ~` - 删除用户目录 - Fork 炸弹 - 直接写入磁盘设备 ### 中风险 - 访问敏感路径（/etc/passwd、~/.ssh/ 等） - 使用 eval/exec ### 低风险 - 网络请求未验证 SSL - 使用 os.system/subprocess ## 安全建议 1. **只安装可信来源的 skill** - 优先使用 clawhub 官方 skill - 检查 GitHub 仓库的 star 数和更新频率 2. **检查 skill 代码** - 阅读 SKILL.md 了解功能 - 检查 scripts/ 目录下的代码 3. **隔离测试** - 新 skill 先在测试环境运行 - 观察是否有异常行为 4. **定期扫描** - 定期运行安全检查 - 及时更新 skill 到最新版本 5. **最小权限原则** - 不给 skill 不必要的权限 - 敏感操作需要确认

path: skill_scan.py
content: #!/usr/bin/env python3
"""
Skill 安全检查工具
扫描已安装的 skill 是否存在潜在风险
"""

import os
import re
import json
from pathlib import Path

class SkillSecurityScanner:
    """Skill 安全扫描器"""
    
    # 危险命令模式
    DANGEROUS_PATTERNS = [
        (r'rm\s+-rf\s+/', '危险: 递归删除根目录'),
        (r'rm\s+-rf\s+~', '危险: 递归删除用户目录'),
        (r'>\s*/dev/sda', '危险: 直接写入磁盘'),
        (r':\(\)\{\s*:\|:&\s*\};:', '危险: Fork 炸弹'),
        (r'curl\s+.*\s*\|\s*sh', '警告: 管道执行远程脚本'),
        (r'wget\s+.*\s*\|\s*sh', '警告: 管道执行远程脚本'),
        (r'eval\s*\(', '警告: 使用 eval'),
        (r'exec\s*\(', '警告: 使用 exec'),
        (r'system\s*\(', '警告: 使用 system 调用'),
        (r'os\.system\s*\(', '警告: 使用 os.system'),
        (r'subprocess\.call\s*\([^)]*shell\s*=\s*True', '警告: subprocess 使用 shell=True'),
    ]
    
    # 敏感文件路径
    SENSITIVE_PATHS = [
        '/etc/passwd',
        '/etc/shadow',
        '/etc/hosts',
        '/.ssh/',
        '/.aws/',
        '/.config/',
        '/.openclaw/',
    ]
    
    def __init__(self, skills_dir='~/.openclaw/workspace/skills'):
        self.skills_dir = Path(skills_dir).expanduser()
        self.issues = []
        
    def scan_all_skills(self):
        """扫描所有 skill"""
        print("=" * 60)
        print("Skill 安全扫描")
        print("=" * 60)
        
        if not self.skills_dir.exists():
            print(f"错误: Skill 目录不存在: {self.skills_dir}")
            return
            
        skills = [d for d in self.skills_dir.iterdir() if d.is_dir()]
        print(f"\n发现 {len(skills)} 个 skill\n")
        
        for skill_path in skills:
            self.scan_skill(skill_path)
            
        self.print_summary()
        
    def scan_skill(self, skill_path):
        """扫描单个 skill"""
        skill_name = skill_path.name
        print(f"\n扫描: {skill_name}")
        print("-" * 40)
        
        skill_issues = []
        
        # 扫描所有文件
        for file_path in skill_path.rglob('*'):
            if file_path.is_file():
                # 跳过文档和示例文件
                if file_path.name in ['SKILL.md', 'README.md', 'CHANGELOG.md']:
                    continue
                if file_path.suffix in ['.md', '.txt', '.rst']:
                    continue
                issues = self.scan_file(file_path, skill_name)
                skill_issues.extend(issues)
                
        if skill_issues:
            for issue in skill_issues:
                print(f"  ! {issue['level']}: {issue['message']}")
                print(f"      文件: {issue['file']}")
                if issue.get('line'):
                    print(f"      行号: {issue['line']}")
        else:
            print("  [OK] 未发现明显风险")
            
        self.issues.extend(skill_issues)
        
    def scan_file(self, file_path, skill_name):
        """扫描单个文件"""
        issues = []
        
        try:
            # 跳过二进制文件
            if self.is_binary(file_path):
                return issues
                
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
                lines = content.split('\n')
                
            # 检查危险模式
            for pattern, message in self.DANGEROUS_PATTERNS:
                for line_num, line in enumerate(lines, 1):
                    if re.search(pattern, line, re.IGNORECASE):
                        issues.append({
                            'skill': skill_name,
                            'file': str(file_path.relative_to(self.skills_dir)),
                            'line': line_num,
                            'level': '高',
                            'message': message,
                            'content': line.strip()[:100]
                        })
                        
            # 检查敏感路径访问
            for sensitive_path in self.SENSITIVE_PATHS:
                if sensitive_path in content:
                    issues.append({
                        'skill': skill_name,
                        'file': str(file_path.relative_to(self.skills_dir)),
                        'level': '中',
                        'message': f'访问敏感路径: {sensitive_path}'
                    })
                    
            # 检查网络请求
            if re.search(r'(curl|wget|requests\.get|urllib\.request)', content, re.IGNORECASE):
                # 检查是否有验证
                if not re.search(r'(verify|cert|ssl|tls)', content, re.IGNORECASE):
                    issues.append({
                        'skill': skill_name,
                        'file': str(file_path.relative_to(self.skills_dir)),
                        'level': '低',
                        'message': '网络请求可能未验证 SSL/TLS'
                    })
                    
        except Exception as e:
            issues.append({
                'skill': skill_name,
                'file': str(file_path.relative_to(self.skills_dir)),
                'level': '信息',
                'message': f'扫描错误: {e}'
            })
            
        return issues
        
    def is_binary(self, file_path):
        """检查是否为二进制文件"""
        binary_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.ico', '.pdf', '.zip', '.tar', '.gz'}
        return file_path.suffix.lower() in binary_extensions
        
    def print_summary(self):
        """打印扫描摘要"""
        print("\n" + "=" * 60)
        print("扫描摘要")
        print("=" * 60)
        
        if not self.issues:
            print("\n[OK] 所有 skill 扫描完成，未发现安全风险")
            return
            
        high = sum(1 for i in self.issues if i['level'] == '高')
        medium = sum(1 for i in self.issues if i['level'] == '中')
        low = sum(1 for i in self.issues if i['level'] == '低')
        
        print(f"\n发现 {len(self.issues)} 个问题:")
        print(f"  [HIGH] 高风险: {high}")
        print(f"  [MED] 中风险: {medium}")
        print(f"  [LOW] 低风险: {low}")
        
        if high > 0:
            print("\n[!] 发现高风险问题，建议立即检查！")
            
        print("\n建议:")
        print("  1. 仔细检查标记的文件和代码")
        print("  2. 确认 skill 来源可信")
        print("  3. 在隔离环境中测试新 skill")
        print("  4. 定期更新 skill 到最新版本")


if __name__ == '__main__':
    scanner = SkillSecurityScanner()
    scanner.scan_all_skills()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)