-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathstreamlit_ui.py
More file actions
289 lines (252 loc) · 9.81 KB
/
streamlit_ui.py
File metadata and controls
289 lines (252 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/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("""
<style>
.main-header {
font-size: 2.5rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 0.5rem;
}
.sub-header {
font-size: 1.1rem;
color: #6b7280;
margin-bottom: 2rem;
}
.metric-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 1.5rem;
border-radius: 1rem;
color: white;
}
.safe-badge {
background-color: #10b981;
color: white;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-weight: 600;
}
.warning-badge {
background-color: #f59e0b;
color: white;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-weight: 600;
}
.danger-badge {
background-color: #ef4444;
color: white;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-weight: 600;
}
.finding-card {
border-left: 4px solid;
padding: 1rem;
margin: 0.5rem 0;
background: #f9fafb;
border-radius: 0 0.5rem 0.5rem 0;
}
.critical { border-color: #ef4444; }
.high { border-color: #f97316; }
.medium { border-color: #f59e0b; }
.low { border-color: #3b82f6; }
.info { border-color: #6b7280; }
</style>
""", 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('<p class="main-header">Skill Scanner</p>', unsafe_allow_html=True)
st.markdown('<p class="sub-header">Security audit tool for Clawdbot/MCP skills - scans for malware, spyware, crypto-mining, and malicious patterns</p>', 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"""
<div class="finding-card {severity}">
<strong style="color: {color};">[{severity.upper()}]</strong>
<strong>{finding.get('category', 'Unknown')}</strong><br/>
<em>{finding.get('file', 'Unknown file')}</em>
{f"(Line {finding.get('line', '?')})" if finding.get('line') else ''}<br/>
{finding.get('description', '')}
</div>
""", unsafe_allow_html=True)
if finding.get('match'):
with st.expander("View matched code"):
st.code(finding.get('match', ''), language='python')
else:
st.info("No security issues found!")
# Export options
st.markdown("---")
st.subheader("Export Report")
col1, col2 = st.columns(2)
with col1:
# Markdown export
if output_format == 'markdown':
from skill_scanner import SkillScanner
scanner = SkillScanner('.')
md_report = scanner.format_markdown(results)
st.download_button(
label="Download Markdown Report",
data=md_report,
file_name="skill_scan_report.md",
mime="text/markdown",
use_container_width=True
)
with col2:
# JSON export
import json
json_report = json.dumps(results, indent=2)
st.download_button(
label="Download JSON Report",
data=json_report,
file_name="skill_scan_report.json",
mime="application/json",
use_container_width=True
)
if __name__ == "__main__":
main()