Skill: niuqun2003/skll-scan (version 1.0.0)

--- name: skill-scan description: | Security scanning tool for OpenClaw Skills. Detects malicious code patterns, extracts domains, and checks threat intelligence APIs. Use when: installing new Skills (pre-installation security check), auditing installed Skills, detecting malicious code, scanning suspicious domains, generating security reports, or verifying Skill safety before distribution. --- # Skill Scan - Security Scanner for OpenClaw Skills ## Purpose Skill Scan is a security tool that analyzes OpenClaw Skills for potentially malicious code and threat intelligence indicators before installation or during audits. ## Features - 🔍 **Static Code Analysis**: Detects dangerous patterns (exec, network, filesystem, sensitive data access) - 🌐 **Domain Extraction**: Identifies all domains referenced in Skill code - 🛡️ **Threat Intelligence Check**: Validates domains against abuse databases (Abuse.ch, AbuseIPDB, VirusTotal) - 📊 **Risk Scoring**: Generates risk levels (low/medium/high) with detailed reports - 💾 **JSON Export**: Saves scan results for automation and CI/CD integration ## Usage ### Basic Scan ```bash python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py <skill-path> ``` ### Examples ```bash # Scan a specific Skill python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py ~/.openclaw/extensions/mem9 # Scan all installed Skills for skill in ~/.openclaw/extensions/*/; do echo "Scanning: $skill" python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py "$skill" done # Scan before installation tar -xzf new-skill.tgz -C /tmp/skill-check/ python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py /tmp/skill-check/ ``` ## Output ### Risk Levels | Level | Meaning | Action | |-------|---------|--------| | 🟢 **low** | Only routine network requests | Safe to install | | 🟡 **medium** | Contains exec/system calls | Review code manually | | 🔴 **high** | Suspicious domains/malicious patterns | ⚠️ Do NOT install | ### Report Format ``` ============================================================ 📊 Skill Security Scan Report ============================================================ Skill Path: /path/to/skill Risk Level: low Total Findings: 2 Domains Checked: 1 📋 Findings by Category: - network: 2 📝 Details: [network] /path/to/file.ts:30 const resp = await fetch(this.baseUrl + "/v1alpha1/mem9s", { ============================================================ ``` ## Detection Categories ### 1. Exec (System Command Execution) - `exec()`, `execSync()`, `spawn()` - `child_process`, `subprocess.*` - `os.system()`, `shell_exec()` ### 2. Network (Network Requests) - `fetch()`, `axios.*` - `http.get`, `https.get` - `requests.*`, `urllib.request` - `XMLHttpRequest` ### 3. Filesystem (File Operations) - `fs.writeFile`, `fs.readFile`, `fs.unlink` - `open(..., 'w')` - `shutil.(copy|move|remove)` ### 4. Sensitive (Sensitive Data Access) - `process.env`, `process.argv` - `os.environ` - Hardcoded secrets: `secret=`, `password=`, `token=`, `api_key=` ## Threat Intelligence Sources ### Built-in (Free APIs) | Source | Type | API | |--------|------|-----| | **Abuse.ch URLhaus** | Malicious domains/IPs | https://urlhaus-api.abuse.ch/ | | **AbuseIPDB** | IP reputation | https://www.abuseipdb.com/api | | **Local Blacklist** | Known malicious domains | Built-in | ### Optional (API Key Required) | Source | Type | API | |--------|------|-----| | **VirusTotal** | Files/URLs/Domains | https://www.virustotal.com/api/ | | **AlienVault OTX** | Threat intelligence | https://otx.alienvault.com/api | | **Google Safe Browsing** | Malicious websites | https://safebrowsing.googleapis.com/ | ## Configuration To enable additional threat intelligence APIs, edit the script and add your API keys: ```python THREAT_INTEL_APIS = { "virustotal": { "url": "https://www.virustotal.com/api/v3/domains/", "key_param": "x-apikey", "api_key": "YOUR_API_KEY" # Add your key here } } ``` ## Integration ### Pre-installation Hook Add to your CI/CD pipeline: ```bash #!/bin/bash # Pre-installation security check SKILL_PATH=$1 REPORT=$(python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py "$SKILL_PATH") if echo "$REPORT" | grep -q "Risk Level: high"; then echo "❌ Security check failed: High risk detected" exit 1 fi echo "✅ Security check passed" ``` ### Periodic Audit Create a cron job for weekly audits: ```bash # /etc/cron.d/skill-scan 0 2 * * 1 niuqun python3 ~/.openclaw/skills/skill-scan/scripts/skill-scan.py ~/.openclaw/extensions/* >> /var/log/skill-scan.log ``` ## Security Best Practices 1. **Always scan before installing** - Never install Skills from unknown sources without scanning 2. **Review medium/high risks** - Don't ignore warnings 3. **Keep threat intel updated** - Regularly update local blacklists 4. **Report false positives** - Help improve the tool 5. **Contribute signatures** - Add new malicious patterns you discover ## Troubleshooting ### Issue: Script fails to run **Solution**: Ensure Python 3 is installed and script has execute permission ```bash chmod +x ~/.openclaw/skills/skill-scan/scripts/skill-scan.py ``` ### Issue: Threat intelligence API timeout **Solution**: Check network connection or API key validity ```bash curl -I https://urlhaus-api.abuse.ch/ ``` ### Issue: Too many false positives **Solution**: Adjust detection patterns in the script or add domains to whitelist ## Limitations - **Static analysis only** - Cannot detect runtime behavior - **API rate limits** - Free APIs have request limits - **Evasion techniques** - Obfuscated code may bypass detection - **No sandbox** - Does not execute code in isolation ## Contributing Contributions welcome! Please: 1. Fork the repository 2. Add new detection patterns 3. Integrate additional threat intelligence sources 4. Submit pull requests ## License MIT License - See LICENSE file for details ## Support - **Issues**: https://github.com/yourusername/skill-scan/issues - **Documentation**: https://github.com/yourusername/skill-scan/wiki - **Threat Intel**: Report malicious domains to [REDACTED_SECRET] --- **Remember**: This tool is a first line of defense. Always combine with manual code review and other security measures for critical systems.

path: references/threat-intel-apis.md
content: # Threat Intelligence APIs Reference

This document lists available threat intelligence APIs for Skill Scan integration.

## Free APIs (No API Key Required)

### Abuse.ch URLhaus

**Purpose**: Check if a domain/IP is associated with malware distribution

**API Endpoint**: `https://urlhaus-api.abuse.ch/v1/host/`

**Example Usage**:
```python
import urllib.request
import urllib.parse
import json

def check_urlhaus(host):
    url = "https://urlhaus-api.abuse.ch/v1/host/"
    data = urllib.parse.urlencode({"host": host}).encode()
    req = urllib.request.Request(url, data)
    
    try:
        with urllib.request.urlopen(req, timeout=5) as resp:
            result = json.loads(resp.read())
            if result.get("query_status") == "ok" and result.get("abuse_confidence"):
                return {"status": "malicious", "source": "abuse.ch"}
    except Exception as e:
        print(f"Error: {e}")
    
    return {"status": "unknown"}
```

**Rate Limits**: None documented (be respectful)

**Documentation**: https://urlhaus-api.abuse.ch/

---

### AbuseIPDB

**Purpose**: IP address reputation checking

**API Endpoint**: `https://api.abuseipdb.com/api/v2/check`

**Authentication**: API Key required (free tier available)

**Example Usage**:
```python
import requests

def check_abuseipdb(ip, api_key):
    url = "https://api.abuseipdb.com/api/v2/check"
    headers = {
        'Key': api_key,
        'Accept': 'application/json'
    }
    params = {
        'ipAddress': ip,
        'maxAgeInDays': '90'
    }
    
    resp = requests.get(url, headers=headers, params=params)
    data = resp.json()
    
    if data['data'].get('abuseConfidenceScore', 0) > 50:
        return {"status": "suspicious", "score": data['data']['abuseConfidenceScore']}
    
    return {"status": "clean"}
```

**Rate Limits**: 1,000 requests/day (free tier)

**Get API Key**: https://www.abuseipdb.com/api

---

## Free APIs (API Key Required)

### VirusTotal

**Purpose**: Comprehensive malware/URL/domain scanning

**API Endpoint**: `https://www.virustotal.com/api/v3/domains/{domain}`

**Authentication**: API Key required

**Example Usage**:
```python
import requests

def check_virustotal(domain, api_key):
    url = f"https://www.virustotal.com/api/v3/domains/{domain}"
    headers = {
        'x-apikey': api_key
    }
    
    resp = requests.get(url, headers=headers)
    data = resp.json()
    
    stats = data['data']['attributes']['last_analysis_stats']
    malicious = stats.get('malicious', 0)
    
    if malicious > 0:
        return {"status": "malicious", "malicious_count": malicious}
    
    return {"status": "clean"}
```

**Rate Limits**: 
- Free: 4 requests/minute, 500 requests/day
- Premium: Higher limits

**Get API Key**: https://www.virustotal.com/gui/join-us

---

### AlienVault OTX

**Purpose**: Open threat intelligence sharing platform

**API Endpoint**: `https://otx.alienvault.com/api/v1/indicators/domain/{domain}`

**Authentication**: API Key required

**Example Usage**:
```python
import requests

def check_otx(domain, api_key):
    url = f"https://otx.alienvault.com/api/v1/indicators/domain/{domain}/general"
    headers = {
        'X-OTX-API-KEY': api_key
    }
    
    resp = requests.get(url, headers=headers)
    data = resp.json()
    
    if data.get('pulse_info', {}).get('count', 0) > 0:
        return {"status": "suspicious", "pulses": data['pulse_info']['count']}
    
    return {"status": "unknown"}
```

**Rate Limits**: Varies by account type

**Get API Key**: https://otx.alienvault.com/accounts/register

---

### Google Safe Browsing

**Purpose**: Check if a URL is flagged as unsafe by Google

**API Endpoint**: `https://safebrowsing.googleapis.com/v4/threatMatches:find`

**Authentication**: API Key required

**Example Usage**:
```python
import requests

def check_safebrowsing(url, api_key):
    endpoint = "https://safebrowsing.googleapis.com/v4/threatMatches:find"
    payload = {
        "client": {
            "clientId": "skill-scan",
            "clientVersion": "1.0.0"
        },
        "threatInfo": {
            "threatTypes": ["MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE"],
            "platformTypes": ["ANY_PLATFORM"],
            "threatEntryTypes": ["URL"],
            "threatEntries": [{"url": url}]
        }
    }
    
    resp = requests.post(f"{endpoint}?key={api_key}", json=payload)
    data = resp.json()
    
    if 'matches' in data:
        return {"status": "malicious", "threats": data['matches']}
    
    return {"status": "clean"}
```

**Rate Limits**: 10,000 requests/day (free tier)

**Get API Key**: https://developers.google.com/safe-browsing/v4/get-started

---

## Local Blacklists

### Built-in Domain Blacklist

Maintain a local list of known malicious domains:

```python
BLACKLIST_DOMAINS = [
    "malware.com",
    "evil.com",
    # Add more as discovered
]

def check_local_blacklist(domain):
    if domain in BLACKLIST_DOMAINS:
        return {"status": "malicious", "source": "local_blacklist"}
    return {"status": "unknown"}
```

### Update Sources

- https://github.com/StevenBlack/hosts
- https://github.com/PolishFiltersTeam/KADhosts
- https://github.com/AdAway/adaway.github.io

---

## API Integration Strategy

### Recommended Flow

```python
def comprehensive_check(domain, api_keys):
    results = {}
    
    # 1. Check local blacklist (fastest)
    results['local'] = check_local_blacklist(domain)
    if results['local']['status'] == 'malicious':
        return results
    
    # 2. Check free APIs (no auth)
    results['urlhaus'] = check_urlhaus(domain)
    if results['urlhaus']['status'] == 'malicious':
        return results
    
    # 3. Check authenticated APIs (slower)
    if api_keys.get('virustotal'):
        results['virustotal'] = check_virustotal(domain, api_keys['virustotal'])
    
    if api_keys.get('abuseipdb'):
        # Extract IP from domain first
        ip = resolve_domain(domain)
        results['abuseipdb'] = check_abuseipdb(ip, api_keys['abuseipdb'])
    
    # 4. Aggregate results
    return aggregate_results(results)

def aggregate_results(results):
    malicious_count = sum(1 for r in results.values() if r.get('status') == 'malicious')
    
    if malicious_count > 0:
        return {"status": "malicious", "details": results}
    elif any(r.get('status') == 'suspicious' for r in results.values()):
        return {"status": "suspicious", "details": results}
    else:
        return {"status": "clean", "details": results}
```

---

## Best Practices

1. **Rate Limiting**: Respect API
   - Add delays between requests
   - Cache results when possible

2. **Error Handling**: Handle API failures gracefully
   - Timeouts
   - Rate limit responses (429)
   - Invalid responses

3. **Privacy**: Don't send sensitive domains to third-party APIs
   - Use local checks for internal domains
   - Consider self-hosted threat intel

4. **Updates**: Keep blacklists and patterns updated
   - Schedule regular updates
   - Subscribe to threat intel feeds

---

## Configuration Example

Create a config file at `~/.skill-scan/config.json`:

```json
{
  "api_keys": {
    "virustotal": "YOUR_VT_API_KEY",
    "abuseipdb": "YOUR_ABUSEIPDB_API_KEY",
    "otx": "YOUR_OTX_API_KEY",
    "safebrowsing": "YOUR_GOOGLE_API_KEY"
  },
  "settings": {
    "timeout": 5,
    "cache_results": true,
    "cache_ttl": 86400,
    "rate_limit_delay": 1
  }
}
```

Load in script:

```python
import json
import os

def load_config():
    config_path = os.path.expanduser("~/.skill-scan/config.json")
    if os.path.exists(config_path):
        with open(config_path) as f:
            return json.load(f)
    return {"api_keys": {}, "settings": {}}
```

---

## Testing

Test API integrations:

```bash
# Test URLhaus
curl -X POST https://urlhaus-api.abuse.ch/v1/host/ \
  -d "host=google.com"

# Test VirusTotal (replace API_KEY)
curl --request GET \
  --url https://www.virustotal.com/api/v3/domains/google.com \
  --header 'x-[REDACTED_SECRET]'
```

---

## Contributing Threat Intel

If you discover malicious domains:

1. **Report to Abuse.ch**: https://abuse.ch/report/
2. **Report to AbuseIPDB**: https://www.abuseipdb.com/report
3. **Report to VirusTotal**: https://www.virustotal.com/gui/file/upload
4. **Add to local blacklist**: Submit PR to this repo

---

**Last Updated**: 2026-03-09
**Version**: 1.0.0


path: scripts/skill-scan.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Skill 安全扫描工具 - 威胁情报检查
用法：python3 skill-scan.py <skill-path>
"""

import os
import sys
import re
import json
import urllib.request
import urllib.error
from pathlib import Path

# 威胁情报 API 列表（免费）
THREAT_INTEL_APIS = {
    "abuse.ch": {
        "url": "https://urlhaus-api.abuse.ch/v1/host/",
        "key_param": None,
        "description": "检查恶意域名/IP"
    },
    "virustotal": {
        "url": "https://www.virustotal.com/api/v3/domains/",
        "key_param": "x-apikey",
        "description": "VirusTotal 域名检查"
    }
}

# 危险代码模式
DANGEROUS_PATTERNS = {
    "exec": [
        r"exec\s*\(",
        r"execSync\s*\(",
        r"spawn\s*\(",
        r"child_process",
        r"subprocess\.",
        r"os\.system\s*\(",
        r"shell_exec\s*\("
    ],
    "network": [
        r"fetch\s*\(",
        r"axios\.",
        r"http\.get",
        r"https\.get",
        r"requests\.(get|post|put|delete)",
        r"urllib\.request",
        r"XMLHttpRequest"
    ],
    "filesystem": [
        r"fs\.writeFile",
        r"fs\.readFile",
        r"fs\.unlink",
        r"open\s*\([^,]+,\s*['\"]w",
        r"shutil\.(copy|move|remove)"
    ],
    "sensitive": [
        r"process\.env",
        r"process\.argv",
        r"os\.environ",
        r"secret\s*=",
        r"password\s*=",
        r"token\s*=",
        r"api[_-]?key\s*="
    ]
}

# 已知恶意域名黑名单（示例）
BLACKLIST_DOMAINS = [
    "malware.com",
    "evil.com",
    "hackers.com"
]

def scan_code_patterns(file_path):
    """扫描文件中的危险代码模式"""
    results = []
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
            for line_num, line in enumerate(lines, 1):
                for category, patterns in DANGEROUS_PATTERNS.items():
                    for pattern in patterns:
                        if re.search(pattern, line, re.IGNORECASE):
                            results.append({
                                "file": str(file_path),
                                "line": line_num,
                                "category": category,
                                "pattern": pattern,
                                "content": line.strip()[:100]
                            })
    except Exception as e:
        print(f"⚠️ 读取文件失败 {file_path}: {e}")
    return results

def extract_domains(file_path):
    """从代码中提取域名"""
    domains = []
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read()
            # 匹配 http(s)://domain
            urls = re.findall(r'https?://([a-zA-Z0-9.-]+)', content)
            domains.extend(urls)
    except Exception as e:
        print(f"⚠️ 提取域名失败 {file_path}: {e}")
    return list(set(domains))

def check_threat_intel(domain):
    """检查威胁情报（简化版，实际应调用 API）"""
    # 检查本地黑名单
    if domain in BLACKLIST_DOMAINS:
        return {"status": "malicious", "source": "local_blacklist"}
    
    # 示例：检查 abuse.ch（需要实际 API 调用）
    # try:
    #     url = f"https://urlhaus-api.abuse.ch/v1/host/"
    #     data = urllib.parse.urlencode({"host": domain}).encode()
    #     req = urllib.request.Request(url, data)
    #     with urllib.request.urlopen(req, timeout=5) as resp:
    #         result = json.loads(resp.read())
    #         if result.get("query_status") == "ok" and result.get("abuse_confidence"):
    #             return {"status": "suspicious", "source": "abuse.ch"}
    # except Exception as e:
    #     print(f"⚠️ 威胁情报检查失败 {domain}: {e}")
    
    return {"status": "unknown", "source": "not_checked"}

def scan_skill(skill_path):
    """扫描整个 Skill 目录"""
    skill_path = Path(skill_path)
    if not skill_path.exists():
        print(f"❌ 路径不存在：{skill_path}")
        return None
    
    print(f"🔍 开始扫描 Skill: {skill_path}\n")
    
    all_findings = []
    all_domains = []
    
    # 扫描所有 .ts/.js/.py 文件
    for ext in ["*.ts", "*.js", "*.py"]:
        for file in skill_path.rglob(ext):
            # 跳过 node_modules
            if "node_modules" in str(file):
                continue
            
            print(f"📄 扫描文件：{file}")
            
            # 代码模式扫描
            findings = scan_code_patterns(file)
            all_findings.extend(findings)
            
            # 提取域名
            domains = extract_domains(file)
            all_domains.extend(domains)
    
    # 域名威胁情报检查
    print(f"\n🌐 发现 {len(all_domains)} 个域名，正在检查威胁情报...")
    domain_checks = {}
    for domain in set(all_domains):
        result = check_threat_intel(domain)
        domain_checks[domain] = result
        if result["status"] != "unknown":
            print(f"  ⚠️ {domain}: {result['status']} ({result['source']})")
    
    # 生成报告
    report = {
        "skill_path": str(skill_path),
        "total_findings": len(all_findings),
        "findings_by_category": {},
        "domains_checked": len(domain_checks),
        "suspicious_domains": [],
        "risk_level": "low",
        "details": all_findings
    }
    
    # 统计各类别数量
    for finding in all_findings:
        cat = finding["category"]
        report["findings_by_category"][cat] = report["findings_by_category"].get(cat, 0) + 1
    
    # 标记可疑域名
    for domain, result in domain_checks.items():
        if result["status"] in ["malicious", "suspicious"]:
            report["suspicious_domains"].append({
                "domain": domain,
                "status": result["status"],
                "source": result["source"]
            })
    
    # 评估风险等级
    if report["suspicious_domains"]:
        report["risk_level"] = "high"
    elif report["findings_by_category"].get("exec", 0) > 0:
        report["risk_level"] = "medium"
    elif report["total_findings"] > 5:
        report["risk_level"] = "low"
    
    return report

def print_report(report):
    """打印扫描报告"""
    print("\n" + "="*60)
    print("📊 Skill 安全扫描报告")
    print("="*60)
    print(f"扫描路径：{report['skill_path']}")
    print(f"风险等级：{report['risk_level']}")
    print(f"发现问题：{report['total_findings']} 个")
    print(f"检查域名：{report['domains_checked']} 个")
    
    if report["findings_by_category"]:
        print("\n📋 问题分类:")
        for cat, count in report["findings_by_category"].items():
            print(f"  - {cat}: {count}")
    
    if report["suspicious_domains"]:
        print("\n⚠️ 可疑域名:")
        for d in report["suspicious_domains"]:
            print(f"  - {d['domain']}: {d['status']} ({d['source']})")
    
    if report["details"]:
        print("\n📝 详细信息:")
        for f in report["details"][:10]:  # 只显示前 10 个
            print(f"  [{f['category']}] {f['file']}:{f['line']}")
            print(f"    {f['content']}")
    
    print("="*60)

def main():
    if len(sys.argv) < 2:
        print("用法：python3 skill-scan.py <skill-path>")
        print("示例：python3 skill-scan.py ~/.openclaw/extensions/my-skill")
        sys.exit(1)
    
    skill_path = sys.argv[1]
    report = scan_skill(skill_path)
    
    if report:
        print_report(report)
        
        # 保存报告
        report_file = f"/tmp/skill-scan-{os.path.basename(skill_path)}.json"
        with open(report_file, 'w') as f:
            json.dump(report, f, indent=2, ensure_ascii=False)
        print(f"\n💾 报告已保存：{report_file}")

if __name__ == "__main__":
    main()


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