#!/usr/bin/env python3 """ Skill Security Scanner - Community audit tool for agent skills Detects credential theft, undeclared network calls, suspicious file access """ import os import re import sys import json from pathlib import Path class SkillScanner: def __init__(self, skill_path): self.skill_path = Path(skill_path) self.findings = [] def scan(self): """Run all security checks""" print(f"šŸ” Scanning {self.skill_path}") self.check_credential_access() self.check_network_calls() self.check_file_operations() self.check_permission_manifest() return self.findings def check_credential_access(self): """Detect patterns that access credential files""" patterns = [ r'\.env', r'\.aws/credentials', r'\.ssh/id_', r'\.clawdbot/\.env', r'OPENAI_API_KEY', r'process\.env\[', ] for file in self.skill_path.rglob('*.py'): content = file.read_text(errors='ignore') for pattern in patterns: if re.search(pattern, content, re.IGNORECASE): self.findings.append({ 'severity': 'HIGH', 'type': 'credential_access', 'file': str(file), 'pattern': pattern, 'message': f'Accesses credentials: {pattern}' }) def check_network_calls(self): """Detect undeclared network operations""" patterns = [ r'requests\.(get|post|put)', r'urllib\.request', r'http\.client', r'socket\.connect', r'webhook\.site', ] for file in self.skill_path.rglob('*.py'): content = file.read_text(errors='ignore') for pattern in patterns: if re.search(pattern, content, re.IGNORECASE): self.findings.append({ 'severity': 'MEDIUM', 'type': 'network_call', 'file': str(file), 'pattern': pattern, 'message': f'Network call: {pattern}' }) def check_file_operations(self): """Detect suspicious file operations""" patterns = [ r'open\(["\']\/.*["\'].*w', # Writing to absolute paths r'os\.remove', r'shutil\.rmtree', r'\.write\(', ] for file in self.skill_path.rglob('*.py'): content = file.read_text(errors='ignore') for pattern in patterns: if re.search(pattern, content): self.findings.append({ 'severity': 'MEDIUM', 'type': 'file_operation', 'file': str(file), 'pattern': pattern, 'message': f'File operation: {pattern}' }) def check_permission_manifest(self): """Check if permissions.json exists and is valid""" manifest = self.skill_path / 'permissions.json' if not manifest.exists(): self.findings.append({ 'severity': 'LOW', 'type': 'missing_manifest', 'file': 'permissions.json', 'message': 'No permission manifest found' }) else: try: perms = json.loads(manifest.read_text()) print(f"āœ… Found permission manifest: {perms}") except json.JSONDecodeError: self.findings.append({ 'severity': 'MEDIUM', 'type': 'invalid_manifest', 'file': 'permissions.json', 'message': 'Permission manifest is invalid JSON' }) def main(): if len(sys.argv) < 2: print("Usage: python scan.py /path/to/skill") sys.exit(1) skill_path = sys.argv[1] scanner = SkillScanner(skill_path) findings = scanner.scan() print(f"\nšŸ“Š Scan Results: {len(findings)} findings\n") for finding in findings: severity_emoji = {'HIGH': 'šŸ”“', 'MEDIUM': '🟔', 'LOW': '⚪'} print(f"{severity_emoji[finding['severity']]} {finding['severity']}: {finding['message']}") print(f" File: {finding['file']}") print() if not findings: print("āœ… No security issues detected!") sys.exit(len([f for f in findings if f['severity'] == 'HIGH'])) if __name__ == '__main__': main()