shadowcat 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent/__init__.py +17 -0
- agent/benchmark/__init__.py +11 -0
- agent/benchmark/cli.py +179 -0
- agent/benchmark/config.py +15 -0
- agent/benchmark/docker.py +192 -0
- agent/benchmark/registry.py +99 -0
- agent/core/__init__.py +0 -0
- agent/core/agent.py +362 -0
- agent/core/backend.py +1667 -0
- agent/core/config.py +106 -0
- agent/core/controller.py +638 -0
- agent/core/events.py +177 -0
- agent/core/langfuse.py +320 -0
- agent/core/phantom.py +2327 -0
- agent/core/planner.py +493 -0
- agent/core/profiling.py +58 -0
- agent/core/sanitizer.py +104 -0
- agent/core/session.py +228 -0
- agent/core/tracer.py +137 -0
- agent/interface/__init__.py +0 -0
- agent/interface/components/__init__.py +0 -0
- agent/interface/components/activity_feed.py +202 -0
- agent/interface/components/renderers.py +149 -0
- agent/interface/components/splash.py +112 -0
- agent/interface/main.py +1126 -0
- agent/interface/styles.tcss +421 -0
- agent/interface/tui.py +508 -0
- agent/parsing/html_distiller.py +230 -0
- agent/parsing/tool_parser.py +115 -0
- agent/prompts/__init__.py +0 -0
- agent/prompts/pentesting.py +238 -0
- agent/rag_module/knowledge_base.py +116 -0
- agent/tests/benchmark_phantom.py +455 -0
- agent/tools/__init__.py +14 -0
- agent/tools/base.py +99 -0
- agent/tools/executor.py +345 -0
- agent/tools/registry.py +47 -0
- backend/README.md +244 -0
- backend/__init__.py +10 -0
- backend/api/__init__.py +1 -0
- backend/api/routes_scan.py +932 -0
- backend/authz.py +75 -0
- backend/cli.py +261 -0
- backend/compliance/__init__.py +1 -0
- backend/compliance/pdpa_mapping.py +16 -0
- backend/core/__init__.py +1 -0
- backend/core/coverage.py +93 -0
- backend/core/events.py +166 -0
- backend/core/llm_client.py +614 -0
- backend/core/orchestrator.py +402 -0
- backend/core/scan_memory.py +236 -0
- backend/crawler/__init__.py +1 -0
- backend/crawler/csrf.py +75 -0
- backend/crawler/headless.py +232 -0
- backend/crawler/js_analyzer.py +133 -0
- backend/crawler/spider.py +550 -0
- backend/crawler/subdomains.py +279 -0
- backend/daemon.py +252 -0
- backend/db/README.md +86 -0
- backend/db/__init__.py +17 -0
- backend/db/engine.py +87 -0
- backend/db/models.py +206 -0
- backend/db/repository.py +316 -0
- backend/db/schema.sql +247 -0
- backend/modes/__init__.py +5 -0
- backend/modes/base.py +178 -0
- backend/modes/ctf.py +39 -0
- backend/modes/enterprise.py +210 -0
- backend/modes/general.py +226 -0
- backend/modes/registry.py +34 -0
- backend/reporting/__init__.py +0 -0
- backend/reporting/generator.py +285 -0
- backend/schemas/__init__.py +1 -0
- backend/schemas/api.py +423 -0
- backend/tools/__init__.py +62 -0
- backend/tools/dirbrute_tool.py +304 -0
- backend/tools/general_report_tool.py +135 -0
- backend/tools/http_tool.py +351 -0
- backend/tools/nuclei_tool.py +20 -0
- backend/tools/report_tool.py +145 -0
- backend/tools/shell_tools.py +23 -0
- backend/verification/__init__.py +1 -0
- backend/verification/evidence_store.py +125 -0
- backend/verification/general_oracle.py +369 -0
- backend/verification/idor_oracle.py +131 -0
- backend/waf/__init__.py +0 -0
- backend/waf/detector.py +147 -0
- backend/waf/evasion.py +117 -0
- backend/webui/index.html +713 -0
- backend/workspace.py +182 -0
- shadowcat-2.0.0.dist-info/METADATA +360 -0
- shadowcat-2.0.0.dist-info/RECORD +95 -0
- shadowcat-2.0.0.dist-info/WHEEL +4 -0
- shadowcat-2.0.0.dist-info/entry_points.txt +4 -0
- shadowcat-2.0.0.dist-info/licenses/LICENSE.md +21 -0
backend/webui/index.html
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>ShadowCat</title>
|
|
7
|
+
<style>
|
|
8
|
+
/* ===== BASE ===== */
|
|
9
|
+
:root {
|
|
10
|
+
--bg:#04050a; --panel:rgba(8,10,18,0.75); --panel2:rgba(14,17,30,0.7); --panel3:rgba(20,24,40,0.8);
|
|
11
|
+
--border:rgba(255,255,255,0.07); --border-accent:rgba(20,184,166,0.4);
|
|
12
|
+
--text:#e2e8f5; --muted:#6b7a99; --faint:#3d4a66;
|
|
13
|
+
--accent:#14b8a6; --accent-dim:rgba(20,184,166,0.6); --accent-glow:rgba(20,184,166,0.25); --accent-ink:#021a18;
|
|
14
|
+
--amber:#f59e0b; --rose:#f43f5e; --green:#10b981; --purple:#8b5cf6; --blue:#3b82f6;
|
|
15
|
+
--mono:ui-monospace,'Cascadia Code',Consolas,monospace;
|
|
16
|
+
--sans:system-ui,-apple-system,'Segoe UI',sans-serif;
|
|
17
|
+
}
|
|
18
|
+
[data-theme="light"] {
|
|
19
|
+
--bg:#f0f4fb; --panel:rgba(255,255,255,0.82); --panel2:rgba(240,244,251,0.9); --panel3:rgba(225,232,248,0.9);
|
|
20
|
+
--border:rgba(0,0,0,0.07); --border-accent:rgba(13,148,136,0.35);
|
|
21
|
+
--text:#0f172a; --muted:#64748b; --faint:#94a3b8;
|
|
22
|
+
}
|
|
23
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
24
|
+
html,body{height:100%;overflow:hidden}
|
|
25
|
+
body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;display:flex;position:relative;}
|
|
26
|
+
::-webkit-scrollbar{width:5px}
|
|
27
|
+
::-webkit-scrollbar-thumb{background:var(--faint);border-radius:3px}
|
|
28
|
+
|
|
29
|
+
/* ===== BACKGROUND ===== */
|
|
30
|
+
.bg-aurora{position:fixed;inset:0;pointer-events:none;z-index:0;overflow:hidden;}
|
|
31
|
+
.blob{position:absolute;border-radius:50%;filter:blur(90px);opacity:0.3;}
|
|
32
|
+
.blob-1{width:700px;height:700px;background:radial-gradient(circle,#8b5cf6 0%,transparent 70%);top:-200px;left:-150px;animation:b1 13s ease-in-out infinite alternate;}
|
|
33
|
+
.blob-2{width:550px;height:550px;background:radial-gradient(circle,#14b8a6 0%,transparent 70%);bottom:-180px;right:80px;animation:b2 11s ease-in-out infinite alternate;}
|
|
34
|
+
.blob-3{width:450px;height:450px;background:radial-gradient(circle,#3b82f6 0%,transparent 70%);top:45%;left:42%;transform:translate(-50%,-50%);animation:b3 15s ease-in-out infinite alternate;}
|
|
35
|
+
@keyframes b1{0%{transform:translate(0,0) scale(1)}100%{transform:translate(70px,50px) scale(1.2)}}
|
|
36
|
+
@keyframes b2{0%{transform:translate(0,0) scale(1.1)}100%{transform:translate(-60px,-40px) scale(0.85)}}
|
|
37
|
+
@keyframes b3{0%{transform:translate(-50%,-50%) scale(1)}100%{transform:translate(-44%,-56%) scale(1.25)}}
|
|
38
|
+
[data-theme="light"] .blob{opacity:0.12;}
|
|
39
|
+
|
|
40
|
+
.bg-grid{
|
|
41
|
+
position:fixed;inset:0;pointer-events:none;z-index:0;
|
|
42
|
+
background-image:radial-gradient(circle,rgba(255,255,255,0.09) 1px,transparent 1px);
|
|
43
|
+
background-size:28px 28px;
|
|
44
|
+
}
|
|
45
|
+
[data-theme="light"] .bg-grid{background-image:radial-gradient(circle,rgba(0,0,0,0.055) 1px,transparent 1px);}
|
|
46
|
+
|
|
47
|
+
#spotlight{
|
|
48
|
+
position:fixed;pointer-events:none;z-index:1;
|
|
49
|
+
width:700px;height:700px;border-radius:50%;
|
|
50
|
+
transform:translate(-50%,-50%);
|
|
51
|
+
background:radial-gradient(circle,rgba(20,184,166,0.07) 0%,transparent 55%);
|
|
52
|
+
transition:opacity .4s;opacity:0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ===== LAYOUT ===== */
|
|
56
|
+
.shell{position:relative;z-index:2;display:flex;width:100%;height:100%;}
|
|
57
|
+
|
|
58
|
+
/* ===== SIDEBAR ===== */
|
|
59
|
+
aside{
|
|
60
|
+
width:224px;flex-shrink:0;
|
|
61
|
+
background:var(--panel);
|
|
62
|
+
backdrop-filter:blur(28px);-webkit-backdrop-filter:blur(28px);
|
|
63
|
+
border-right:1px solid var(--border);
|
|
64
|
+
display:flex;flex-direction:column;
|
|
65
|
+
}
|
|
66
|
+
.brand{display:flex;align-items:center;gap:11px;padding:20px 16px 14px;}
|
|
67
|
+
.brand-icon{
|
|
68
|
+
width:38px;height:38px;border-radius:11px;
|
|
69
|
+
background:linear-gradient(135deg,rgba(20,184,166,0.25),rgba(139,92,246,0.25));
|
|
70
|
+
border:1px solid rgba(20,184,166,0.28);
|
|
71
|
+
display:flex;align-items:center;justify-content:center;font-size:19px;
|
|
72
|
+
box-shadow:0 0 22px rgba(20,184,166,0.18),inset 0 1px 0 rgba(255,255,255,0.08);
|
|
73
|
+
}
|
|
74
|
+
.brand-text .sup{font-size:9px;color:var(--faint);letter-spacing:2px;text-transform:uppercase;}
|
|
75
|
+
.brand-text .name{
|
|
76
|
+
font-size:17px;font-weight:700;letter-spacing:-.4px;
|
|
77
|
+
background:linear-gradient(110deg,#e2e8f5 20%,#14b8a6 80%);
|
|
78
|
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;
|
|
79
|
+
}
|
|
80
|
+
[data-theme="light"] .brand-text .name{background:linear-gradient(110deg,#0f172a 20%,#0d9488 80%);-webkit-background-clip:text;background-clip:text;}
|
|
81
|
+
|
|
82
|
+
/* Rotating border on New Scan */
|
|
83
|
+
@property --ra{syntax:'<angle>';initial-value:0deg;inherits:false;}
|
|
84
|
+
@keyframes spin{to{--ra:360deg}}
|
|
85
|
+
.nbwrap{
|
|
86
|
+
margin:2px 14px 14px;border-radius:10px;padding:1.5px;
|
|
87
|
+
background:conic-gradient(from var(--ra),transparent 25%,#14b8a6 50%,#8b5cf6 60%,transparent 75%);
|
|
88
|
+
animation:spin 4s linear infinite;
|
|
89
|
+
}
|
|
90
|
+
.newbtn{
|
|
91
|
+
width:100%;padding:10px;border:none;border-radius:9px;cursor:pointer;
|
|
92
|
+
background:linear-gradient(135deg,rgba(20,184,166,0.14),rgba(139,92,246,0.1));
|
|
93
|
+
color:var(--accent);font-weight:600;font-size:13px;letter-spacing:.3px;
|
|
94
|
+
transition:background .2s;position:relative;
|
|
95
|
+
}
|
|
96
|
+
.newbtn:hover{background:linear-gradient(135deg,rgba(20,184,166,0.26),rgba(139,92,246,0.18));}
|
|
97
|
+
|
|
98
|
+
nav{flex:1;overflow-y:auto;padding:4px 10px;}
|
|
99
|
+
.navsect{color:var(--faint);font-size:9.5px;text-transform:uppercase;letter-spacing:1.8px;padding:14px 10px 6px;font-weight:600;}
|
|
100
|
+
.navitem{
|
|
101
|
+
display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:9px;
|
|
102
|
+
cursor:pointer;color:var(--muted);font-size:13px;transition:all .15s;
|
|
103
|
+
position:relative;margin-bottom:1px;border:1px solid transparent;
|
|
104
|
+
}
|
|
105
|
+
.navitem:hover{background:rgba(255,255,255,0.04);color:var(--text);}
|
|
106
|
+
[data-theme="light"] .navitem:hover{background:rgba(0,0,0,0.04);}
|
|
107
|
+
.navitem.active{
|
|
108
|
+
background:linear-gradient(135deg,rgba(20,184,166,0.12),rgba(139,92,246,0.07));
|
|
109
|
+
color:var(--accent);border-color:rgba(20,184,166,0.15);
|
|
110
|
+
}
|
|
111
|
+
.navitem.active::before{
|
|
112
|
+
content:'';position:absolute;left:0;top:22%;bottom:22%;width:2.5px;border-radius:2px;
|
|
113
|
+
background:linear-gradient(to bottom,#14b8a6,#8b5cf6);
|
|
114
|
+
}
|
|
115
|
+
.navitem .ic{width:16px;text-align:center;font-size:14px;}
|
|
116
|
+
.count{
|
|
117
|
+
margin-left:auto;font-size:10.5px;padding:1px 8px;border-radius:20px;
|
|
118
|
+
background:rgba(20,184,166,0.1);color:var(--accent);border:1px solid rgba(20,184,166,0.18);
|
|
119
|
+
}
|
|
120
|
+
.navitem:not(.active) .count{background:var(--panel3);color:var(--muted);border-color:var(--border);}
|
|
121
|
+
|
|
122
|
+
.footer{
|
|
123
|
+
padding:12px 16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);
|
|
124
|
+
display:flex;align-items:center;gap:8px;background:rgba(0,0,0,0.12);
|
|
125
|
+
}
|
|
126
|
+
.dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;transition:all .3s;}
|
|
127
|
+
.dot.on{background:var(--green);box-shadow:0 0 9px var(--green);}
|
|
128
|
+
.dot.off{background:var(--rose);box-shadow:0 0 9px var(--rose);}
|
|
129
|
+
|
|
130
|
+
/* ===== TOPBAR ===== */
|
|
131
|
+
main{flex:1;display:flex;flex-direction:column;min-width:0;}
|
|
132
|
+
.topbar{
|
|
133
|
+
padding:11px 24px;border-bottom:1px solid var(--border);
|
|
134
|
+
display:flex;align-items:center;gap:10px;font-size:13px;
|
|
135
|
+
background:rgba(4,5,10,0.45);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
|
|
136
|
+
}
|
|
137
|
+
[data-theme="light"] .topbar{background:rgba(240,244,251,0.7);}
|
|
138
|
+
.crumb{color:var(--faint);}
|
|
139
|
+
.crumb b{
|
|
140
|
+
background:linear-gradient(90deg,var(--text),var(--accent));
|
|
141
|
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:600;
|
|
142
|
+
}
|
|
143
|
+
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px;}
|
|
144
|
+
.idle-status{font-size:12px;color:var(--faint);display:flex;align-items:center;gap:6px;}
|
|
145
|
+
.idle-dot{width:6px;height:6px;border-radius:50%;background:var(--faint);}
|
|
146
|
+
.theme-btn{
|
|
147
|
+
background:var(--panel2);border:1px solid var(--border);color:var(--muted);
|
|
148
|
+
border-radius:8px;cursor:pointer;padding:5px 9px;font-size:14px;
|
|
149
|
+
transition:all .15s;backdrop-filter:blur(8px);
|
|
150
|
+
}
|
|
151
|
+
.theme-btn:hover{border-color:var(--accent-dim);color:var(--accent);}
|
|
152
|
+
.badge{
|
|
153
|
+
font-size:10px;padding:3px 10px;border-radius:20px;text-transform:uppercase;
|
|
154
|
+
letter-spacing:.5px;background:var(--panel2);color:var(--muted);border:1px solid var(--border);
|
|
155
|
+
}
|
|
156
|
+
.badge.running,.badge.queued,.badge.verifying{color:var(--accent);border-color:var(--border-accent);}
|
|
157
|
+
.badge.completed{color:var(--green);border-color:rgba(16,185,129,.4);}
|
|
158
|
+
.badge.failed,.badge.cancelled{color:var(--rose);border-color:rgba(244,63,94,.4);}
|
|
159
|
+
.stopbtn{
|
|
160
|
+
padding:5px 13px;border-radius:7px;cursor:pointer;font-size:12px;font-weight:600;
|
|
161
|
+
background:rgba(244,63,94,.1);color:var(--rose);border:1px solid rgba(244,63,94,.35);transition:background .12s;
|
|
162
|
+
}
|
|
163
|
+
.stopbtn:hover{background:rgba(244,63,94,.22);}
|
|
164
|
+
.stopbtn:disabled{opacity:.5;cursor:default;}
|
|
165
|
+
.view{flex:1;overflow-y:auto;padding:24px;}
|
|
166
|
+
.hidden{display:none !important;}
|
|
167
|
+
|
|
168
|
+
/* ===== STATUS BAR ===== */
|
|
169
|
+
.statusbar{
|
|
170
|
+
display:grid;grid-template-columns:repeat(auto-fit,minmax(108px,1fr));gap:8px;
|
|
171
|
+
flex-shrink:0;padding:12px 24px;border-bottom:1px solid var(--border);
|
|
172
|
+
background:rgba(4,5,10,0.35);backdrop-filter:blur(14px);
|
|
173
|
+
}
|
|
174
|
+
[data-theme="light"] .statusbar{background:rgba(240,244,251,0.65);}
|
|
175
|
+
.stile{
|
|
176
|
+
background:var(--panel2);border:1px solid var(--border);border-radius:10px;
|
|
177
|
+
padding:10px 13px;backdrop-filter:blur(8px);transition:border-color .2s;
|
|
178
|
+
}
|
|
179
|
+
.stile:hover{border-color:rgba(20,184,166,0.22);}
|
|
180
|
+
.stile .sk{font-size:9px;color:var(--faint);text-transform:uppercase;letter-spacing:1.2px;}
|
|
181
|
+
.stile .sv{font-size:19px;font-weight:700;margin-top:4px;display:flex;align-items:center;gap:7px;letter-spacing:-.5px;}
|
|
182
|
+
.stile .sv.mono{font-family:var(--mono);font-size:16px;}
|
|
183
|
+
.stile .sv.amber{color:var(--amber);}
|
|
184
|
+
.stile .sv.green{color:var(--green);}
|
|
185
|
+
.stile .sv #stStatus{font-size:13px;text-transform:capitalize;font-weight:600;}
|
|
186
|
+
.livedot{width:8px;height:8px;border-radius:50%;background:var(--faint);flex-shrink:0;}
|
|
187
|
+
.livedot.live{background:var(--accent);box-shadow:0 0 10px var(--accent);animation:pulse 1.4s ease-in-out infinite;}
|
|
188
|
+
.livedot.done{background:var(--green);box-shadow:0 0 10px var(--green);}
|
|
189
|
+
.livedot.fail{background:var(--rose);box-shadow:0 0 10px var(--rose);}
|
|
190
|
+
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.7)}}
|
|
191
|
+
|
|
192
|
+
/* ===== LIVE FEED ===== */
|
|
193
|
+
#feed{display:flex;flex-direction:column;gap:7px;max-width:900px;}
|
|
194
|
+
.ev{display:flex;gap:10px;align-items:flex-start;}
|
|
195
|
+
.ev .ic{width:20px;flex-shrink:0;text-align:center;font-size:12px;margin-top:2px;color:var(--muted);}
|
|
196
|
+
.ev .lbl{font-size:9.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.7px;}
|
|
197
|
+
.ev .txt{margin-top:2px;line-height:1.6;word-wrap:break-word;overflow-wrap:anywhere;}
|
|
198
|
+
.ev.tool .txt{font-family:var(--mono);font-size:12px;color:var(--accent);}
|
|
199
|
+
.ev.result .txt{font-family:var(--mono);font-size:11.5px;color:var(--muted);}
|
|
200
|
+
.ev.finding{
|
|
201
|
+
background:linear-gradient(135deg,rgba(245,158,11,.07),transparent);
|
|
202
|
+
border:1px solid rgba(245,158,11,.2);border-radius:10px;padding:10px 12px;
|
|
203
|
+
}
|
|
204
|
+
.ev.finding .lbl{color:var(--amber);}
|
|
205
|
+
.ev.verified .txt b.confirmed{color:var(--green);}
|
|
206
|
+
.ev.verified .txt b.likely{color:var(--amber);}
|
|
207
|
+
.ev.verified .txt b.refuted,.ev.verified .txt b.inconclusive{color:var(--muted);}
|
|
208
|
+
.ev.err .txt{color:var(--rose);}
|
|
209
|
+
|
|
210
|
+
/* ===== EMPTY STATE ===== */
|
|
211
|
+
.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:60px 20px;min-height:420px;}
|
|
212
|
+
.empty-cat{width:88px;height:88px;margin-bottom:22px;position:relative;}
|
|
213
|
+
.empty-cat svg{width:100%;height:100%;filter:drop-shadow(0 0 20px rgba(20,184,166,0.35));}
|
|
214
|
+
.empty-title{
|
|
215
|
+
font-size:22px;font-weight:700;margin-bottom:8px;
|
|
216
|
+
background:linear-gradient(110deg,var(--text) 30%,#14b8a6 70%,#8b5cf6 100%);
|
|
217
|
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;
|
|
218
|
+
}
|
|
219
|
+
.empty-sub{font-size:13px;color:var(--muted);margin-bottom:28px;line-height:1.6;}
|
|
220
|
+
.scards{display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:420px;}
|
|
221
|
+
.scard{
|
|
222
|
+
position:relative;overflow:hidden;
|
|
223
|
+
background:var(--panel2);border:1px solid var(--border);border-radius:13px;
|
|
224
|
+
padding:18px 16px;cursor:pointer;text-align:left;
|
|
225
|
+
backdrop-filter:blur(10px);transition:all .22s;
|
|
226
|
+
}
|
|
227
|
+
.scard::before{
|
|
228
|
+
content:'';position:absolute;inset:0;border-radius:inherit;
|
|
229
|
+
background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(20,184,166,0.13) 0%,transparent 55%);
|
|
230
|
+
opacity:0;transition:opacity .3s;
|
|
231
|
+
}
|
|
232
|
+
.scard:hover{border-color:rgba(20,184,166,0.38);transform:translateY(-3px);box-shadow:0 12px 40px rgba(20,184,166,0.14);}
|
|
233
|
+
.scard:hover::before{opacity:1;}
|
|
234
|
+
.scard-icon{font-size:22px;margin-bottom:10px;}
|
|
235
|
+
.scard-label{font-size:13px;font-weight:500;color:var(--text);}
|
|
236
|
+
.scard-sub{font-size:11.5px;color:var(--muted);margin-top:3px;}
|
|
237
|
+
|
|
238
|
+
/* ===== LISTS ===== */
|
|
239
|
+
.list{display:flex;flex-direction:column;gap:6px;max-width:900px;}
|
|
240
|
+
.item{
|
|
241
|
+
background:var(--panel2);border:1px solid var(--border);border-radius:11px;
|
|
242
|
+
padding:13px 16px;cursor:pointer;display:flex;align-items:center;gap:12px;
|
|
243
|
+
transition:all .15s;backdrop-filter:blur(8px);
|
|
244
|
+
}
|
|
245
|
+
.item:hover{border-color:rgba(20,184,166,0.3);background:linear-gradient(135deg,rgba(20,184,166,0.06),transparent);transform:translateX(3px);}
|
|
246
|
+
.item .grow{flex:1;min-width:0;}
|
|
247
|
+
.item .ttl{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500;}
|
|
248
|
+
.item .sub{font-size:11.5px;color:var(--muted);margin-top:3px;font-family:var(--mono);}
|
|
249
|
+
.sevdot{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
|
|
250
|
+
h3.sec{
|
|
251
|
+
font-size:10.5px;letter-spacing:1.8px;text-transform:uppercase;
|
|
252
|
+
color:var(--faint);font-weight:600;margin:0 0 14px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ===== MEMORY ===== */
|
|
256
|
+
.memcard{
|
|
257
|
+
background:var(--panel2);border:1px solid var(--border);border-radius:12px;
|
|
258
|
+
padding:16px 18px;margin-bottom:10px;max-width:900px;backdrop-filter:blur(8px);
|
|
259
|
+
transition:border-color .2s;
|
|
260
|
+
}
|
|
261
|
+
.memcard:hover{border-color:rgba(20,184,166,0.2);}
|
|
262
|
+
.memhost{font-size:14px;font-weight:600;font-family:var(--mono);color:var(--accent);}
|
|
263
|
+
.memmeta{font-size:11.5px;color:var(--muted);margin-top:3px;}
|
|
264
|
+
.memsub{font-size:9px;color:var(--faint);text-transform:uppercase;letter-spacing:1.2px;margin:12px 0 6px;font-weight:600;}
|
|
265
|
+
.chips{display:flex;flex-wrap:wrap;gap:5px;}
|
|
266
|
+
.chip{
|
|
267
|
+
font-family:var(--mono);font-size:11px;
|
|
268
|
+
background:rgba(20,184,166,0.08);border:1px solid rgba(20,184,166,0.14);
|
|
269
|
+
border-radius:5px;padding:2px 8px;color:var(--muted);
|
|
270
|
+
}
|
|
271
|
+
.chip b{color:var(--accent);}
|
|
272
|
+
.chip.more{color:var(--faint);background:var(--panel3);border-color:var(--border);}
|
|
273
|
+
.memfind{font-size:12.5px;padding:5px 0;display:flex;align-items:center;gap:9px;}
|
|
274
|
+
|
|
275
|
+
/* ===== MODAL ===== */
|
|
276
|
+
.overlay{
|
|
277
|
+
position:fixed;inset:0;background:rgba(0,0,0,0.7);
|
|
278
|
+
backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
|
|
279
|
+
display:none;align-items:center;justify-content:center;z-index:100;
|
|
280
|
+
}
|
|
281
|
+
.overlay.show{display:flex;}
|
|
282
|
+
.modal{
|
|
283
|
+
background:rgba(8,10,20,0.96);border:1px solid rgba(255,255,255,0.09);
|
|
284
|
+
border-radius:18px;padding:28px;width:490px;max-width:92vw;
|
|
285
|
+
backdrop-filter:blur(30px);
|
|
286
|
+
box-shadow:0 30px 90px rgba(0,0,0,0.7),0 0 0 1px rgba(20,184,166,0.1);
|
|
287
|
+
}
|
|
288
|
+
[data-theme="light"] .modal{background:rgba(255,255,255,0.96);border:1px solid rgba(0,0,0,0.09);box-shadow:0 30px 90px rgba(0,0,0,0.12);}
|
|
289
|
+
.modal h2{margin:0 0 6px;font-size:19px;display:flex;align-items:center;gap:9px;color:var(--text);}
|
|
290
|
+
.modal p{margin:0 0 18px;color:var(--muted);font-size:12.5px;line-height:1.6;}
|
|
291
|
+
label{display:block;font-size:9.5px;color:var(--faint);margin:14px 0 5px;text-transform:uppercase;letter-spacing:1.2px;font-weight:600;}
|
|
292
|
+
input,select,textarea{
|
|
293
|
+
width:100%;padding:9px 12px;
|
|
294
|
+
background:rgba(255,255,255,0.04);border:1px solid var(--border);
|
|
295
|
+
border-radius:8px;color:var(--text);font-family:var(--sans);font-size:13px;transition:border-color .15s,box-shadow .15s;
|
|
296
|
+
}
|
|
297
|
+
[data-theme="light"] input,[data-theme="light"] select,[data-theme="light"] textarea{background:rgba(0,0,0,0.04);}
|
|
298
|
+
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(20,184,166,0.15);}
|
|
299
|
+
textarea{resize:vertical;min-height:52px;}
|
|
300
|
+
.frow{display:flex;gap:10px;}.frow>div{flex:1;}
|
|
301
|
+
.actions{display:flex;justify-content:flex-end;gap:10px;margin-top:24px;}
|
|
302
|
+
.btn{
|
|
303
|
+
padding:9px 20px;border-radius:9px;cursor:pointer;font-weight:600;font-size:13px;
|
|
304
|
+
border:1px solid var(--border);background:rgba(255,255,255,0.05);color:var(--text);transition:all .15s;
|
|
305
|
+
}
|
|
306
|
+
.btn:hover{background:rgba(255,255,255,0.09);border-color:rgba(255,255,255,0.14);}
|
|
307
|
+
.btn.primary{
|
|
308
|
+
background:linear-gradient(135deg,#14b8a6,#0891b2);color:#fff;border-color:transparent;
|
|
309
|
+
box-shadow:0 0 24px rgba(20,184,166,0.35);
|
|
310
|
+
}
|
|
311
|
+
.btn.primary:hover{background:linear-gradient(135deg,#2dd4bf,#06b6d4);box-shadow:0 0 36px rgba(20,184,166,0.55);transform:translateY(-1px);}
|
|
312
|
+
|
|
313
|
+
/* ===== ICONS ===== */
|
|
314
|
+
.ico{display:inline-flex;align-items:center;justify-content:center;line-height:0;flex-shrink:0;}
|
|
315
|
+
.ico svg{width:1em;height:1em;stroke:currentColor;fill:none;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round;}
|
|
316
|
+
.navitem .ic{font-size:17px;}
|
|
317
|
+
.brand-icon .ico{font-size:21px;color:var(--accent);}
|
|
318
|
+
.newbtn .ico{font-size:15px;}
|
|
319
|
+
.theme-btn .ico{font-size:15px;}
|
|
320
|
+
.scard-icon .ico{font-size:22px;color:var(--accent);}
|
|
321
|
+
.ev .ic .ico{font-size:14px;}
|
|
322
|
+
.stopbtn .ico{font-size:13px;}
|
|
323
|
+
.ev.reason .ic{color:var(--purple);} .ev.tool .ic{color:var(--accent);}
|
|
324
|
+
.ev.finding .ic{color:var(--amber);} .ev.verified .ic{color:var(--green);}
|
|
325
|
+
.ev.err .ic{color:var(--rose);} .ev.result .ic{color:var(--muted);}
|
|
326
|
+
</style>
|
|
327
|
+
</head>
|
|
328
|
+
<body>
|
|
329
|
+
|
|
330
|
+
<div class="bg-aurora" aria-hidden="true">
|
|
331
|
+
<div class="blob blob-1"></div>
|
|
332
|
+
<div class="blob blob-2"></div>
|
|
333
|
+
<div class="blob blob-3"></div>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="bg-grid" aria-hidden="true"></div>
|
|
336
|
+
<div id="spotlight" aria-hidden="true"></div>
|
|
337
|
+
|
|
338
|
+
<div class="shell">
|
|
339
|
+
|
|
340
|
+
<!-- ===== SIDEBAR ===== -->
|
|
341
|
+
<aside>
|
|
342
|
+
<div class="brand">
|
|
343
|
+
<div class="brand-icon">
|
|
344
|
+
<span class="ico"><svg viewBox="0 0 24 24"><path d="M4 4l3.4 4.3M20 4l-3.4 4.3"/><path d="M5 11a7 7 0 0 1 14 0v3a7 7 0 0 1-14 0z"/><circle cx="9.5" cy="12" r=".6" fill="currentColor" stroke="none"/><circle cx="14.5" cy="12" r=".6" fill="currentColor" stroke="none"/><path d="M12 14.5v1"/></svg></span>
|
|
345
|
+
</div>
|
|
346
|
+
<div class="brand-text">
|
|
347
|
+
<div class="sup">Control</div>
|
|
348
|
+
<div class="name">ShadowCat</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<div class="nbwrap">
|
|
353
|
+
<button class="newbtn" onclick="openModal('newscan')">
|
|
354
|
+
<span class="ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="M12 1v3M12 20v3M1 12h3M20 12h3"/></svg></span>
|
|
355
|
+
New Scan
|
|
356
|
+
</button>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<nav>
|
|
360
|
+
<div class="navsect">Scan</div>
|
|
361
|
+
<div class="navitem active" data-view="feed" onclick="showView('feed')">
|
|
362
|
+
<span class="ic ico"><svg viewBox="0 0 24 24"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></span> Live Feed
|
|
363
|
+
</div>
|
|
364
|
+
<div class="navitem" data-view="sessions" onclick="showView('sessions')">
|
|
365
|
+
<span class="ic ico"><svg viewBox="0 0 24 24"><path d="M3 12a9 9 0 1 0 3-7.7L3 8"/><path d="M3 4v4h4"/><path d="M12 7v5l3 2"/></svg></span> Sessions
|
|
366
|
+
<span class="count" id="cnt-sessions"></span>
|
|
367
|
+
</div>
|
|
368
|
+
<div class="navsect">Results</div>
|
|
369
|
+
<div class="navitem" data-view="findings" onclick="showView('findings')">
|
|
370
|
+
<span class="ic ico"><svg viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><path d="M4 22v-7"/></svg></span> Findings
|
|
371
|
+
<span class="count" id="cnt-findings"></span>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="navitem" data-view="memory" onclick="showView('memory')">
|
|
374
|
+
<span class="ic ico"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 5v14a9 3 0 0 1-18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/></svg></span> Memory
|
|
375
|
+
<span class="count" id="cnt-memory"></span>
|
|
376
|
+
</div>
|
|
377
|
+
</nav>
|
|
378
|
+
|
|
379
|
+
<div class="footer">
|
|
380
|
+
<span class="dot" id="ddot"></span>
|
|
381
|
+
<span id="dtext">checking…</span>
|
|
382
|
+
</div>
|
|
383
|
+
</aside>
|
|
384
|
+
|
|
385
|
+
<!-- ===== MAIN ===== -->
|
|
386
|
+
<main>
|
|
387
|
+
<div class="topbar">
|
|
388
|
+
<span class="crumb">ShadowCat › <b id="crumbView">Live Feed</b></span>
|
|
389
|
+
<span id="crumbTarget" class="crumb"></span>
|
|
390
|
+
<div class="topbar-right">
|
|
391
|
+
<div class="idle-status" id="idleStatus">
|
|
392
|
+
<span class="idle-dot"></span>Idle · no active scan
|
|
393
|
+
</div>
|
|
394
|
+
<button class="stopbtn hidden" id="stopBtn" onclick="stopScan()"><span class="ico"><svg viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2"/></svg></span> Stop scan</button>
|
|
395
|
+
<span class="badge hidden" id="topBadge"></span>
|
|
396
|
+
<button class="theme-btn" id="themeBtn" onclick="toggleTheme()" title="Toggle theme" aria-label="Toggle theme"></button>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<div class="statusbar hidden" id="statusbar">
|
|
401
|
+
<div class="stile"><div class="sk">Status</div><div class="sv"><span class="livedot" id="liveDot"></span><span id="stStatus">—</span></div></div>
|
|
402
|
+
<div class="stile"><div class="sk">Elapsed</div><div class="sv mono" id="stElapsed">0:00</div></div>
|
|
403
|
+
<div class="stile"><div class="sk">Tool calls</div><div class="sv" id="stTools">0</div></div>
|
|
404
|
+
<div class="stile"><div class="sk">Findings</div><div class="sv amber" id="stFindings">0</div></div>
|
|
405
|
+
<div class="stile"><div class="sk">Confirmed</div><div class="sv green" id="stConfirmed">0</div></div>
|
|
406
|
+
<div class="stile"><div class="sk">Spend</div><div class="sv mono" id="stCost">$0.0000</div></div>
|
|
407
|
+
<div class="stile"><div class="sk">Tokens</div><div class="sv mono" id="stTokens">0</div></div>
|
|
408
|
+
<div class="stile"><div class="sk">Events</div><div class="sv" id="stEvents">0</div></div>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
<!-- LIVE FEED -->
|
|
412
|
+
<div class="view" id="view-feed">
|
|
413
|
+
<div id="feed"></div>
|
|
414
|
+
<div class="empty" id="emptyState">
|
|
415
|
+
<div class="empty-cat">
|
|
416
|
+
<svg viewBox="0 0 512 512" aria-hidden="true">
|
|
417
|
+
<defs>
|
|
418
|
+
<radialGradient id="eg" cx="50%" cy="50%" r="50%">
|
|
419
|
+
<stop offset="0%" stop-color="#14b8a6" stop-opacity=".55"/>
|
|
420
|
+
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
|
|
421
|
+
</radialGradient>
|
|
422
|
+
</defs>
|
|
423
|
+
<path d="M256 150C206 150 184 196 190 246 156 300 126 360 140 408 168 432 208 422 256 422 304 422 344 432 372 408 386 360 356 300 322 246 328 196 306 150Z" fill="#111827"/>
|
|
424
|
+
<path d="M196 210L156 104 242 182Z" fill="#111827"/>
|
|
425
|
+
<path d="M316 210L356 104 270 182Z" fill="#111827"/>
|
|
426
|
+
<ellipse cx="212" cy="246" rx="40" ry="30" fill="url(#eg)"/>
|
|
427
|
+
<ellipse cx="300" cy="246" rx="40" ry="30" fill="url(#eg)"/>
|
|
428
|
+
<circle cx="212" cy="246" r="22" fill="#e0faf6"/>
|
|
429
|
+
<circle cx="300" cy="246" r="22" fill="#e0faf6"/>
|
|
430
|
+
<circle cx="212" cy="246" r="22" fill="none" stroke="#14b8a6" stroke-width="3"/>
|
|
431
|
+
<circle cx="300" cy="246" r="22" fill="none" stroke="#14b8a6" stroke-width="3"/>
|
|
432
|
+
<circle cx="212" cy="248" r="9" fill="#050810"/>
|
|
433
|
+
<circle cx="300" cy="248" r="9" fill="#050810"/>
|
|
434
|
+
</svg>
|
|
435
|
+
</div>
|
|
436
|
+
<div class="empty-title">ShadowCat is watching from the shadows.</div>
|
|
437
|
+
<div class="empty-sub">Start a scan to see the agent work, live · <b style="color:var(--accent)">+ New Scan</b></div>
|
|
438
|
+
<div class="scards">
|
|
439
|
+
<div class="scard" onclick="openModal('newscan')" onmousemove="glow(event,this)">
|
|
440
|
+
<div class="scard-icon"><span class="ico"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="M12 1v3M12 20v3M1 12h3M20 12h3"/></svg></span></div>
|
|
441
|
+
<div class="scard-label">Scan a web target</div>
|
|
442
|
+
<div class="scard-sub">Start a new pentest</div>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="scard" onclick="showView('findings')" onmousemove="glow(event,this)">
|
|
445
|
+
<div class="scard-icon"><span class="ico"><svg viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><path d="M4 22v-7"/></svg></span></div>
|
|
446
|
+
<div class="scard-label">Browse findings</div>
|
|
447
|
+
<div class="scard-sub">Review vulnerabilities</div>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div class="view hidden" id="view-sessions"></div>
|
|
454
|
+
<div class="view hidden" id="view-findings"></div>
|
|
455
|
+
<div class="view hidden" id="view-memory"></div>
|
|
456
|
+
</main>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<!-- MODAL -->
|
|
460
|
+
<div class="overlay" id="overlay-newscan">
|
|
461
|
+
<div class="modal">
|
|
462
|
+
<h2><span class="ico" style="font-size:19px;color:var(--accent)"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="M12 1v3M12 20v3M1 12h3M20 12h3"/></svg></span> New Scan</h2>
|
|
463
|
+
<p>ShadowCat only tests what you authorize. Provide the target and scope below.</p>
|
|
464
|
+
<label>Target URL or host</label>
|
|
465
|
+
<input id="f_target" placeholder="https://target.example.com"/>
|
|
466
|
+
<div class="frow">
|
|
467
|
+
<div><label>Mode</label><select id="f_mode"><option value="general">general</option><option value="enterprise">enterprise</option></select></div>
|
|
468
|
+
<div><label>Model (optional)</label><input id="f_model" placeholder="server default"/></div>
|
|
469
|
+
</div>
|
|
470
|
+
<label>Authorized scope (comma-separated, optional)</label>
|
|
471
|
+
<input id="f_scope" placeholder="target.example.com, 10.0.0.0/24"/>
|
|
472
|
+
<label>Instruction / focus (optional)</label>
|
|
473
|
+
<textarea id="f_instruction" placeholder="e.g. focus on /api/orders"></textarea>
|
|
474
|
+
<div class="actions">
|
|
475
|
+
<button class="btn" onclick="closeModal('newscan')">Cancel</button>
|
|
476
|
+
<button class="btn primary" onclick="startScan()">Start Scan</button>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<script>
|
|
482
|
+
const API=location.origin;
|
|
483
|
+
const TOKEN=new URLSearchParams(location.hash.slice(1)).get("token")||"";
|
|
484
|
+
let activeId=null,es=null,scans=[],liveTimer=null,liveStart=0,scanActive=false;
|
|
485
|
+
let counts={tools:0,findings:0,confirmed:0,events:0};
|
|
486
|
+
const TERMINAL=new Set(["completed","failed","cancelled","error"]);
|
|
487
|
+
const H=j=>{const h={};if(j)h["Content-Type"]="application/json";if(TOKEN)h["Authorization"]="Bearer "+TOKEN;return h;};
|
|
488
|
+
const esc=s=>(s??"").toString().replace(/[&<>]/g,c=>({"&":"&","<":"<",">":">"}[c]));
|
|
489
|
+
const isRunning=s=>["queued","running","verifying"].includes(s);
|
|
490
|
+
const sevColor={confirmed:"var(--green)",likely:"var(--amber)",high:"var(--rose)",medium:"var(--amber)",info:"var(--muted)"};
|
|
491
|
+
|
|
492
|
+
/* ===== inline Lucide-style icon registry ===== */
|
|
493
|
+
const SVG=p=>`<span class="ico"><svg viewBox="0 0 24 24">${p}</svg></span>`;
|
|
494
|
+
const ICON={
|
|
495
|
+
sun:SVG('<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>'),
|
|
496
|
+
moon:SVG('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'),
|
|
497
|
+
reason:SVG('<path d="M12 3l1.8 4.4L18 9l-4.2 1.6L12 15l-1.8-4.4L6 9l4.2-1.6z"/>'),
|
|
498
|
+
tool:SVG('<path d="M4 17l6-5-6-5"/><path d="M13 19h7"/>'),
|
|
499
|
+
result:SVG('<path d="M9 10l-5 5 5 5"/><path d="M20 4v7a4 4 0 0 1-4 4H4"/>'),
|
|
500
|
+
finding:SVG('<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><path d="M4 22v-7"/>'),
|
|
501
|
+
verified:SVG('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="M9 12l2 2 4-4"/>'),
|
|
502
|
+
status:SVG('<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/>'),
|
|
503
|
+
done:SVG('<path d="M20 6L9 17l-5-5"/>'),
|
|
504
|
+
error:SVG('<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4M12 17h.01"/>'),
|
|
505
|
+
blocked:SVG('<circle cx="12" cy="12" r="9"/><path d="M5.6 5.6l12.8 12.8"/>'),
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/* spotlight */
|
|
509
|
+
document.addEventListener("mousemove",e=>{
|
|
510
|
+
const s=document.getElementById("spotlight");
|
|
511
|
+
s.style.left=e.clientX+"px";s.style.top=e.clientY+"px";s.style.opacity="1";
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
/* card glow */
|
|
515
|
+
function glow(e,el){
|
|
516
|
+
const r=el.getBoundingClientRect();
|
|
517
|
+
el.style.setProperty("--mx",((e.clientX-r.left)/r.width*100).toFixed(1)+"%");
|
|
518
|
+
el.style.setProperty("--my",((e.clientY-r.top)/r.height*100).toFixed(1)+"%");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* theme */
|
|
522
|
+
function toggleTheme(){
|
|
523
|
+
const h=document.documentElement,dark=h.dataset.theme==="dark";
|
|
524
|
+
h.dataset.theme=dark?"light":"dark";
|
|
525
|
+
document.getElementById("themeBtn").innerHTML=dark?ICON.sun:ICON.moon;
|
|
526
|
+
localStorage.setItem("sc-theme",h.dataset.theme);
|
|
527
|
+
}
|
|
528
|
+
(()=>{
|
|
529
|
+
const s=localStorage.getItem("sc-theme")||"dark";
|
|
530
|
+
document.documentElement.dataset.theme=s;
|
|
531
|
+
window.addEventListener("DOMContentLoaded",()=>{document.getElementById("themeBtn").innerHTML=s==="dark"?ICON.moon:ICON.sun;});
|
|
532
|
+
})();
|
|
533
|
+
|
|
534
|
+
/* daemon */
|
|
535
|
+
async function checkDaemon(){
|
|
536
|
+
try{const r=await fetch(API+"/health"),ok=r.ok;
|
|
537
|
+
document.getElementById("ddot").className="dot "+(ok?"on":"off");
|
|
538
|
+
document.getElementById("dtext").textContent=ok?"daemon · healthy":"daemon · error";
|
|
539
|
+
}catch{document.getElementById("ddot").className="dot off";document.getElementById("dtext").textContent="daemon · unreachable";}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* views */
|
|
543
|
+
function showView(name){
|
|
544
|
+
document.querySelectorAll(".navitem").forEach(n=>n.classList.toggle("active",n.dataset.view===name));
|
|
545
|
+
["feed","sessions","findings","memory"].forEach(v=>document.getElementById("view-"+v).classList.toggle("hidden",v!==name));
|
|
546
|
+
document.getElementById("statusbar").classList.toggle("hidden",!(name==="feed"&&scanActive));
|
|
547
|
+
document.getElementById("crumbView").textContent={feed:"Live Feed",sessions:"Sessions",findings:"Findings",memory:"Memory"}[name];
|
|
548
|
+
if(name==="sessions")renderSessions();
|
|
549
|
+
if(name==="findings")renderFindings();
|
|
550
|
+
if(name==="memory")renderMemory();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* scans */
|
|
554
|
+
async function loadScans(){
|
|
555
|
+
try{scans=await(await fetch(API+"/api/scans",{headers:H()})).json();}catch{scans=[];}
|
|
556
|
+
document.getElementById("cnt-sessions").textContent=scans.length||"";
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function renderSessions(){
|
|
560
|
+
const v=document.getElementById("view-sessions");
|
|
561
|
+
if(!scans.length){v.innerHTML=`<h3 class="sec">Sessions</h3><div style="color:var(--muted)">No scans yet. Hit <b>+ New Scan</b>.</div>`;return;}
|
|
562
|
+
v.innerHTML=`<h3 class="sec">Sessions · ${scans.length}</h3><div class="list">`+
|
|
563
|
+
scans.map(s=>`<div class="item" onclick="selectScan('${s.scan_id}','${esc(s.target)}','${s.status}')">
|
|
564
|
+
<div class="sevdot" style="background:${isRunning(s.status)?'var(--accent)':s.status==='completed'?'var(--green)':'var(--faint)'}"></div>
|
|
565
|
+
<div class="grow"><div class="ttl">${esc(s.target)}</div><div class="sub">${esc(s.mode)} · ${esc(s.scan_id).slice(0,8)}</div></div>
|
|
566
|
+
<span class="badge ${s.status}">${esc(s.status)}</span></div>`).join("")+`</div>`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function aggregateFindings(){
|
|
570
|
+
const out=[];
|
|
571
|
+
for(const s of scans){
|
|
572
|
+
if(isRunning(s.status))continue;
|
|
573
|
+
try{
|
|
574
|
+
const d=await(await fetch(API+"/api/scans/"+s.scan_id,{headers:H()})).json();
|
|
575
|
+
const v={};(d.result?.extra?.verdicts||[]).forEach(x=>v[x.finding_id]=x.status);
|
|
576
|
+
(d.result?.findings||[]).forEach(f=>out.push({scan_id:s.scan_id,target:s.target,title:f.title||f.vuln_class?.cwe||"finding",cwe:f.vuln_class?.cwe||"",verdict:v[f.finding_id]||f.status||"pending"}));
|
|
577
|
+
}catch{}
|
|
578
|
+
}
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function renderFindings(){
|
|
583
|
+
const v=document.getElementById("view-findings");
|
|
584
|
+
v.innerHTML=`<h3 class="sec">Findings</h3><div style="color:var(--muted)">Loading…</div>`;
|
|
585
|
+
const all=await aggregateFindings();
|
|
586
|
+
document.getElementById("cnt-findings").textContent=all.length||"";
|
|
587
|
+
if(!all.length){v.innerHTML=`<h3 class="sec">Findings</h3><div style="color:var(--muted)">No findings yet. Run a scan to completion.</div>`;return;}
|
|
588
|
+
v.innerHTML=`<h3 class="sec">Findings · ${all.length}</h3><div class="list">`+
|
|
589
|
+
all.map(f=>`<div class="item" onclick="selectScan('${f.scan_id}','${esc(f.target)}','')">
|
|
590
|
+
<div class="sevdot" style="background:${sevColor[f.verdict]||'var(--muted)'}"></div>
|
|
591
|
+
<div class="grow"><div class="ttl">${esc(f.title)}</div><div class="sub">${esc(f.cwe)} · ${esc(f.target)}</div></div>
|
|
592
|
+
<span class="badge ${f.verdict}">${esc(f.verdict)}</span></div>`).join("")+`</div>`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async function renderMemory(){
|
|
596
|
+
const v=document.getElementById("view-memory");
|
|
597
|
+
v.innerHTML=`<h3 class="sec">Memory</h3><div style="color:var(--muted)">Loading…</div>`;
|
|
598
|
+
let mem=[];
|
|
599
|
+
try{mem=await(await fetch(API+"/api/memory",{headers:H()})).json();}catch{}
|
|
600
|
+
document.getElementById("cnt-memory").textContent=mem.length||"";
|
|
601
|
+
if(!mem.length){v.innerHTML=`<h3 class="sec">Memory</h3><div style="color:var(--muted);line-height:1.7;max-width:540px">Nothing remembered yet.<br>After a scan completes, what the agent learned is stored here and reused on the next scan of the same host.</div>`;return;}
|
|
602
|
+
v.innerHTML=`<h3 class="sec">Memory · ${mem.length} target(s)</h3>`+
|
|
603
|
+
`<div style="color:var(--muted);font-size:12px;margin:-6px 0 16px;line-height:1.6">The scanner gets smarter over time — this is what the next scan of each host starts with.</div>`+
|
|
604
|
+
mem.map(k=>{
|
|
605
|
+
const eps=Object.entries(k.endpoints||{}).sort((a,b)=>b[1]-a[1]);
|
|
606
|
+
const epList=eps.slice(0,12).map(([ep,n])=>`<span class="chip">${esc(ep)} <b>×${n}</b></span>`).join("");
|
|
607
|
+
const findList=(k.findings||[]).map(f=>`<div class="memfind"><span class="badge ${esc(f.verdict||'')}">${esc(f.verdict||'?')}</span> ${esc(f.title||'?')}${f.cwe?` <span style="color:var(--faint)">[${esc(f.cwe)}]</span>`:''}</div>`).join("")||`<div style="color:var(--faint);font-size:12px">no findings recorded yet</div>`;
|
|
608
|
+
return `<div class="memcard"><div class="memhost">${esc(k.host)}</div><div class="memmeta">scanned <b>${k.scan_count}×</b> · last ${k.last_scanned?esc(k.last_scanned.slice(0,16).replace('T',' ')):'—'}${k.tech?.waf?` · WAF: ${esc(k.tech.waf)}`:''}</div><div class="memsub">Known endpoints (${eps.length})</div><div class="chips">${epList||'<span style="color:var(--faint);font-size:12px">none yet</span>'}${eps.length>12?`<span class="chip more">+${eps.length-12} more</span>`:''}</div><div class="memsub">Findings (${(k.findings||[]).length})</div>${findList}</div>`;
|
|
609
|
+
}).join("");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/* live feed */
|
|
613
|
+
function setActive(target,status){
|
|
614
|
+
document.getElementById("crumbTarget").textContent=target?"· "+target:"";
|
|
615
|
+
const tb=document.getElementById("topBadge");
|
|
616
|
+
tb.className="badge "+(status||"");tb.textContent=status||"";tb.classList.toggle("hidden",!status);
|
|
617
|
+
document.getElementById("idleStatus").classList.toggle("hidden",!!target);
|
|
618
|
+
}
|
|
619
|
+
const fmtElapsed=ms=>{const s=Math.floor(ms/1000),m=Math.floor(s/60),ss=String(s%60).padStart(2,"0");return m<60?m+":"+ss:Math.floor(m/60)+":"+String(m%60).padStart(2,"0")+":"+ss;};
|
|
620
|
+
const fmtCost=c=>"$"+(c||0).toFixed(c<1?4:2);
|
|
621
|
+
const fmtTokens=(n,est)=>{n=n||0;const s=n>=1e6?(n/1e6).toFixed(2)+"M":n>=1e3?(n/1e3).toFixed(1)+"K":String(n);return(est?"~":"")+s;};
|
|
622
|
+
function stopTimer(){if(liveTimer){clearInterval(liveTimer);liveTimer=null;}}
|
|
623
|
+
function paintCounts(){
|
|
624
|
+
document.getElementById("stTools").textContent=counts.tools;
|
|
625
|
+
document.getElementById("stFindings").textContent=counts.findings;
|
|
626
|
+
document.getElementById("stConfirmed").textContent=counts.confirmed;
|
|
627
|
+
document.getElementById("stEvents").textContent=counts.events;
|
|
628
|
+
}
|
|
629
|
+
function setStatus(st){
|
|
630
|
+
if(!st)return;
|
|
631
|
+
document.getElementById("stStatus").textContent=st;
|
|
632
|
+
const term=TERMINAL.has(st)||st==="cancelled";
|
|
633
|
+
document.getElementById("liveDot").className="livedot "+(term?(st==="completed"?"done":"fail"):"live");
|
|
634
|
+
document.getElementById("stopBtn").classList.toggle("hidden",term);
|
|
635
|
+
if(term){stopTimer();document.getElementById("idleStatus").classList.remove("hidden");}
|
|
636
|
+
}
|
|
637
|
+
function startStatus(status){
|
|
638
|
+
counts={tools:0,findings:0,confirmed:0,events:0};paintCounts();
|
|
639
|
+
scanActive=true;
|
|
640
|
+
document.getElementById("statusbar").classList.remove("hidden");
|
|
641
|
+
document.getElementById("stElapsed").textContent="0:00";
|
|
642
|
+
document.getElementById("stCost").textContent="$0.0000";
|
|
643
|
+
document.getElementById("stTokens").textContent="0";
|
|
644
|
+
liveStart=Date.now();stopTimer();
|
|
645
|
+
liveTimer=setInterval(()=>{document.getElementById("stElapsed").textContent=fmtElapsed(Date.now()-liveStart);},1000);
|
|
646
|
+
setStatus(status||"queued");
|
|
647
|
+
}
|
|
648
|
+
async function stopScan(){
|
|
649
|
+
if(!activeId)return;
|
|
650
|
+
const btn=document.getElementById("stopBtn");
|
|
651
|
+
btn.disabled=true;btn.innerHTML=ICON.status+" Stopping…";
|
|
652
|
+
try{await fetch(API+"/api/scans/"+activeId+"/cancel",{method:"POST",headers:H()});}catch{}
|
|
653
|
+
btn.disabled=false;btn.innerHTML=`<span class="ico"><svg viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2"/></svg></span> Stop scan`;
|
|
654
|
+
loadScans();
|
|
655
|
+
}
|
|
656
|
+
function addEvent(type,p,ts){
|
|
657
|
+
if(ts){const t=Date.parse(ts);if(!isNaN(t)&&(!liveStart||t<liveStart)){liveStart=t;if(scanActive)document.getElementById("stElapsed").textContent=fmtElapsed(Date.now()-liveStart);}}
|
|
658
|
+
if(type==="run.cost"){
|
|
659
|
+
if(typeof p.cost_usd==="number")document.getElementById("stCost").textContent=fmtCost(p.cost_usd);
|
|
660
|
+
if(typeof p.tokens==="number")document.getElementById("stTokens").textContent=fmtTokens(p.tokens,p.tokens_estimated);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
document.getElementById("emptyState")?.remove();
|
|
664
|
+
const el=document.createElement("div");let ic=ICON.status,cls="",lbl=type,txt="";
|
|
665
|
+
if(type==="agent.reasoning"){ic=ICON.reason;cls="reason";lbl=p.tool?"reasoning · "+p.tool:"reasoning";txt=esc(p.text);}
|
|
666
|
+
else if(type==="tool.call"){ic=p.blocked?ICON.blocked:ICON.tool;cls="tool";lbl=p.blocked?"blocked":"tool call";txt=esc(p.name)+" "+esc(JSON.stringify(p.args_summary||{}));if(!p.blocked)counts.tools++;}
|
|
667
|
+
else if(type==="tool.result"){ic=ICON.result;cls="result";lbl="result";txt=esc(p.status??"")+(p.length!=null?" · "+p.length+"b":"")+(p.request_id?" · "+p.request_id:"");}
|
|
668
|
+
else if(type==="finding.candidate"){ic=ICON.finding;cls="finding";lbl="candidate finding";txt=esc(p.finding_id)+(p.cited_request_ids?.length?" — cites "+p.cited_request_ids.join(", "):"");counts.findings++;}
|
|
669
|
+
else if(type==="finding.verified"){ic=ICON.verified;cls="verified";lbl="verdict";const st=esc(p.status);txt=`<b class="${st}">${st}</b> — ${esc(p.reason||"")}`;if(p.status==="confirmed")counts.confirmed++;}
|
|
670
|
+
else if(type==="run.status"){ic=ICON.status;cls="result";lbl="status";txt=esc(p.status);setActive(document.getElementById("crumbTarget").textContent.replace(/^· /,""),p.status);setStatus(p.status);}
|
|
671
|
+
else if(type==="run.completed"){ic=ICON.done;cls="result";lbl="completed";txt=esc(p.status)+(p.summary?" · "+esc(JSON.stringify(p.summary)):"");if(typeof p.cost_usd==="number")document.getElementById("stCost").textContent=fmtCost(p.cost_usd);if(typeof p.tokens==="number")document.getElementById("stTokens").textContent=fmtTokens(p.tokens,false);setStatus(p.status||"completed");loadScans();}
|
|
672
|
+
else if(type==="error"){ic=ICON.error;cls="err";lbl="error";txt=esc(p.message);}
|
|
673
|
+
else return;
|
|
674
|
+
counts.events++;paintCounts();
|
|
675
|
+
el.className="ev "+cls;
|
|
676
|
+
el.innerHTML=`<div class="ic">${ic}</div><div style="flex:1;min-width:0"><div class="lbl">${esc(lbl)}</div><div class="txt">${txt}</div></div>`;
|
|
677
|
+
const sc=document.getElementById("view-feed");
|
|
678
|
+
const nb=sc.scrollHeight-sc.scrollTop-sc.clientHeight<140;
|
|
679
|
+
document.getElementById("feed").appendChild(el);
|
|
680
|
+
if(nb)sc.scrollTop=sc.scrollHeight;
|
|
681
|
+
}
|
|
682
|
+
function selectScan(id,target,status){
|
|
683
|
+
activeId=id;showView("feed");
|
|
684
|
+
setActive(target,status);document.getElementById("feed").innerHTML="";
|
|
685
|
+
startStatus(status);
|
|
686
|
+
if(es){es.close();es=null;}
|
|
687
|
+
es=new EventSource(API+"/api/scans/"+id+"/stream");
|
|
688
|
+
es.onmessage=m=>{try{const d=JSON.parse(m.data);addEvent(d.type,d.payload||{},d.ts);}catch{}};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function openModal(n){document.getElementById("overlay-"+n).classList.add("show");}
|
|
692
|
+
function closeModal(n){document.getElementById("overlay-"+n).classList.remove("show");}
|
|
693
|
+
|
|
694
|
+
async function startScan(){
|
|
695
|
+
const target=document.getElementById("f_target").value.trim();
|
|
696
|
+
if(!target){document.getElementById("f_target").focus();return;}
|
|
697
|
+
const body={mode:document.getElementById("f_mode").value,target,
|
|
698
|
+
scope:document.getElementById("f_scope").value.split(",").map(s=>s.trim()).filter(Boolean),
|
|
699
|
+
instruction:document.getElementById("f_instruction").value.trim()||null,
|
|
700
|
+
model:document.getElementById("f_model").value.trim()||null};
|
|
701
|
+
try{
|
|
702
|
+
const r=await fetch(API+"/api/scans",{method:"POST",headers:H(true),body:JSON.stringify(body)});
|
|
703
|
+
if(!r.ok){alert("Failed: "+r.status+"\n"+await r.text());return;}
|
|
704
|
+
const{scan_id}=await r.json();closeModal("newscan");
|
|
705
|
+
await loadScans();selectScan(scan_id,target,"queued");
|
|
706
|
+
}catch(e){alert("Could not reach the daemon: "+e);}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
checkDaemon();loadScans();
|
|
710
|
+
setInterval(()=>{checkDaemon();loadScans();},5000);
|
|
711
|
+
</script>
|
|
712
|
+
</body>
|
|
713
|
+
</html>
|