#!/usr/bin/env python3 """ Skill Scanner - Streamlit Web UI A user-friendly interface for scanning Clawdbot/MCP skills for security issues. """ import streamlit as st import tempfile import os import zipfile import shutil from pathlib import Path # Import the scanner try: from skill_scanner import SkillScanner except ImportError: st.error("skill_scanner.py must be in the same directory as this file") st.stop() # Page configuration st.set_page_config( page_title="Skill Scanner", page_icon="", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS for modern look st.markdown(""" """, unsafe_allow_html=True) def get_severity_color(severity: str) -> str: """Get color for severity level.""" colors = { 'critical': '#ef4444', 'high': '#f97316', 'medium': '#f59e0b', 'low': '#3b82f6', 'info': '#6b7280' } return colors.get(severity.lower(), '#6b7280') def get_verdict_display(verdict: str): """Get styled verdict display.""" if verdict == 'APPROVED': return '**APPROVED** - No security issues detected', 'success' elif verdict == 'CAUTION': return '**CAUTION** - Minor issues found, review recommended', 'warning' else: return '**REJECT** - Security issues detected', 'error' def main(): # Header st.markdown('
Skill Scanner
', unsafe_allow_html=True) st.markdown('Security audit tool for Clawdbot/MCP skills - scans for malware, spyware, crypto-mining, and malicious patterns
', unsafe_allow_html=True) # Sidebar with st.sidebar: st.header("Settings") output_format = st.selectbox("Output Format", ["Markdown", "JSON"], index=0) show_info = st.checkbox("Show Info-level findings", value=False) st.markdown("---") st.markdown("### About") st.markdown("This tool scans skill files for potential security issues including:") st.markdown("- Data exfiltration patterns") st.markdown("- System modification attempts") st.markdown("- Crypto-mining indicators") st.markdown("- Arbitrary code execution risks") st.markdown("- Backdoors and obfuscation") # Main content tab1, tab2 = st.tabs(["Scan Files", "Scan Text"]) with tab1: st.subheader("Upload Skill Files") uploaded_files = st.file_uploader( "Upload skill files or a ZIP archive", accept_multiple_files=True, type=['py', 'js', 'ts', 'sh', 'bash', 'md', 'txt', 'json', 'yaml', 'yml', 'zip'], help="Supports Python, JavaScript, TypeScript, Shell scripts, and ZIP archives" ) if uploaded_files: if st.button("Scan Files", type="primary", use_container_width=True): with st.spinner("Scanning files for security issues..."): # Create temp directory with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) for uploaded_file in uploaded_files: file_path = temp_path / uploaded_file.name if uploaded_file.name.endswith('.zip'): # Extract ZIP file with open(file_path, 'wb') as f: f.write(uploaded_file.getvalue()) with zipfile.ZipFile(file_path, 'r') as zip_ref: zip_ref.extractall(temp_path) os.remove(file_path) else: with open(file_path, 'wb') as f: f.write(uploaded_file.getvalue()) # Run scanner scanner = SkillScanner(str(temp_path)) results = scanner.scan() display_results(results, output_format.lower(), show_info) with tab2: st.subheader("Paste Code for Analysis") code_input = st.text_area( "Paste code to scan", height=300, placeholder="Paste your skill code here...", help="Paste any code snippet to scan for security issues" ) if code_input: if st.button("Scan Code", type="primary", use_container_width=True, key="scan_text"): with st.spinner("Analyzing code..."): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) code_file = temp_path / "code_snippet.py" code_file.write_text(code_input) scanner = SkillScanner(str(temp_path)) results = scanner.scan() display_results(results, output_format.lower(), show_info) def display_results(results: dict, output_format: str, show_info: bool): """Display scan results in a user-friendly format.""" st.markdown("---") st.header("Scan Results") # Summary metrics col1, col2, col3, col4 = st.columns(4) findings = results.get('findings', []) if not show_info: findings = [f for f in findings if f.get('severity', '').lower() != 'info'] severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0, 'info': 0} for finding in results.get('findings', []): sev = finding.get('severity', 'info').lower() if sev in severity_counts: severity_counts[sev] += 1 with col1: st.metric("Critical", severity_counts['critical']) with col2: st.metric("High", severity_counts['high']) with col3: st.metric("Medium", severity_counts['medium']) with col4: st.metric("Low", severity_counts['low']) # Verdict verdict = results.get('verdict', 'UNKNOWN') verdict_text, verdict_type = get_verdict_display(verdict) if verdict_type == 'success': st.success(verdict_text) elif verdict_type == 'warning': st.warning(verdict_text) else: st.error(verdict_text) # Files scanned files_scanned = results.get('files_scanned', []) if files_scanned: with st.expander(f"Files Scanned ({len(files_scanned)})", expanded=False): for f in files_scanned: st.text(f"- {f}") # Detailed findings if findings: st.subheader(f"Findings ({len(findings)})") for i, finding in enumerate(findings): severity = finding.get('severity', 'info').lower() color = get_severity_color(severity) with st.container(): st.markdown(f"""