codevanta 0.1.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.
- codevanta/__init__.py +1 -0
- codevanta/analysis/__init__.py +1 -0
- codevanta/analysis/binary_diff.py +1168 -0
- codevanta/analysis/callgraph.py +542 -0
- codevanta/analysis/cfg.py +1201 -0
- codevanta/analysis/dataflow.py +762 -0
- codevanta/analysis/debugger.py +289 -0
- codevanta/analysis/debuginfo.py +304 -0
- codevanta/analysis/decompile.py +1094 -0
- codevanta/analysis/decompilers/__init__.py +250 -0
- codevanta/analysis/decompilers/angr_backend.py +251 -0
- codevanta/analysis/decompilers/base.py +278 -0
- codevanta/analysis/decompilers/ghidra_backend.py +316 -0
- codevanta/analysis/dynamic.py +756 -0
- codevanta/analysis/emulate.py +1349 -0
- codevanta/analysis/evidence_bundle.py +736 -0
- codevanta/analysis/function_hash.py +562 -0
- codevanta/analysis/graph_export.py +441 -0
- codevanta/analysis/html_view.py +695 -0
- codevanta/analysis/jump_tables.py +546 -0
- codevanta/analysis/solver.py +715 -0
- codevanta/analysis/symbolic.py +1809 -0
- codevanta/analysis/trace.py +646 -0
- codevanta/analysis/type_propagation.py +1055 -0
- codevanta/analysis/type_recovery.py +1214 -0
- codevanta/analysis/vtables.py +572 -0
- codevanta/analysis/xrefs.py +434 -0
- codevanta/api/__init__.py +1 -0
- codevanta/cli/__init__.py +1 -0
- codevanta/cli/main.py +3038 -0
- codevanta/core/__init__.py +31 -0
- codevanta/core/disassembler.py +121 -0
- codevanta/core/entropy.py +237 -0
- codevanta/core/functions.py +176 -0
- codevanta/core/loader.py +1351 -0
- codevanta/core/report.py +306 -0
- codevanta/core/strings.py +109 -0
- codevanta/evidence/__init__.py +7 -0
- codevanta/evidence/confidence.py +36 -0
- codevanta/evidence/models.py +43 -0
- codevanta/evidence/provenance.py +37 -0
- codevanta/gui/__init__.py +46 -0
- codevanta/gui/layout.py +1384 -0
- codevanta/gui/server.py +1305 -0
- codevanta/ir/__init__.py +6 -0
- codevanta/ir/adapters/__init__.py +31 -0
- codevanta/ir/base.py +31 -0
- codevanta/ir/instruction.py +42 -0
- codevanta/mcp_server/__init__.py +5 -0
- codevanta/mcp_server/server.py +710 -0
- codevanta/mcp_server/tools.py +1364 -0
- codevanta/plugins/__init__.py +748 -0
- codevanta/setup/__init__.py +28 -0
- codevanta/setup/apply.py +22 -0
- codevanta/setup/clients.py +285 -0
- codevanta/setup/plan.py +79 -0
- codevanta/setup/verify.py +43 -0
- codevanta/storage/__init__.py +1 -0
- codevanta/storage/session.py +514 -0
- codevanta-0.1.0.dist-info/METADATA +688 -0
- codevanta-0.1.0.dist-info/RECORD +65 -0
- codevanta-0.1.0.dist-info/WHEEL +5 -0
- codevanta-0.1.0.dist-info/entry_points.txt +2 -0
- codevanta-0.1.0.dist-info/licenses/LICENSE +21 -0
- codevanta-0.1.0.dist-info/top_level.txt +1 -0
codevanta/gui/layout.py
ADDED
|
@@ -0,0 +1,1384 @@
|
|
|
1
|
+
"""Self-contained HTML dashboard for the local CODEVANTA web UI.
|
|
2
|
+
|
|
3
|
+
The page is a single string with inline CSS and JavaScript and **zero external
|
|
4
|
+
references**: no CDN, no font, no image, no ``http://``/``https://`` URL. That
|
|
5
|
+
keeps the dashboard usable offline (the whole point of a local, read-only
|
|
6
|
+
binary-analysis tool) and makes it trivially testable.
|
|
7
|
+
|
|
8
|
+
Security posture of the generated page:
|
|
9
|
+
|
|
10
|
+
* Every API response is rendered with ``textContent`` (plus ``textContent`` on
|
|
11
|
+
every cell the table and summary builders create), so a hostile binary
|
|
12
|
+
cannot inject markup through a symbol name, a string literal or an error
|
|
13
|
+
message.
|
|
14
|
+
* The only network target is the same-origin ``/api/*`` route table served by
|
|
15
|
+
:mod:`codevanta.gui.server`; the page never contacts a third party.
|
|
16
|
+
* Nothing is uploaded: the file paths typed into the form are resolved by the
|
|
17
|
+
server process, and analysis is strictly read-only.
|
|
18
|
+
|
|
19
|
+
:func:`render_dashboard_html` is the only public entry point.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
__all__ = ["render_dashboard_html"]
|
|
25
|
+
|
|
26
|
+
_STYLE = """
|
|
27
|
+
:root {
|
|
28
|
+
color-scheme: dark;
|
|
29
|
+
--bg: #0a0c10;
|
|
30
|
+
--card: #10141b;
|
|
31
|
+
--card-2: #161b25;
|
|
32
|
+
--line: #1e2634;
|
|
33
|
+
--line-2: #2c3648;
|
|
34
|
+
--ink: #dde6f2;
|
|
35
|
+
--mut: #9aa8bc;
|
|
36
|
+
--dim: #66748c;
|
|
37
|
+
--acc: #2dd4bf;
|
|
38
|
+
--acc-strong: #14b8a6;
|
|
39
|
+
--acc-bg: rgba(45, 212, 191, 0.09);
|
|
40
|
+
--err: #f87171;
|
|
41
|
+
--ok: #4ade80;
|
|
42
|
+
--warn: #fbbf24;
|
|
43
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
44
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
45
|
+
"Helvetica Neue", Arial, sans-serif;
|
|
46
|
+
--radius: 10px;
|
|
47
|
+
}
|
|
48
|
+
* { box-sizing: border-box; }
|
|
49
|
+
html { height: 100%; }
|
|
50
|
+
body {
|
|
51
|
+
margin: 0;
|
|
52
|
+
min-height: 100%;
|
|
53
|
+
padding: 0 0 56px;
|
|
54
|
+
font-family: var(--sans);
|
|
55
|
+
font-size: 14px;
|
|
56
|
+
line-height: 1.5;
|
|
57
|
+
background:
|
|
58
|
+
radial-gradient(1100px 480px at 85% -10%, rgba(45, 212, 191, 0.07), transparent 60%),
|
|
59
|
+
radial-gradient(900px 420px at -10% 0%, rgba(99, 102, 241, 0.05), transparent 55%),
|
|
60
|
+
var(--bg);
|
|
61
|
+
color: var(--ink);
|
|
62
|
+
}
|
|
63
|
+
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 28px; }
|
|
64
|
+
.hidden { display: none !important; }
|
|
65
|
+
|
|
66
|
+
/* ---- top bar ---------------------------------------------------------- */
|
|
67
|
+
.topbar {
|
|
68
|
+
border-bottom: 1px solid var(--line);
|
|
69
|
+
background: rgba(10, 12, 16, 0.85);
|
|
70
|
+
position: sticky; top: 0; z-index: 5;
|
|
71
|
+
}
|
|
72
|
+
.topbar .wrap {
|
|
73
|
+
display: flex; align-items: center; gap: 16px;
|
|
74
|
+
padding-top: 14px; padding-bottom: 14px;
|
|
75
|
+
}
|
|
76
|
+
.mark {
|
|
77
|
+
width: 38px; height: 38px; flex: none;
|
|
78
|
+
display: grid; place-items: center;
|
|
79
|
+
border-radius: 10px;
|
|
80
|
+
border: 1px solid rgba(45, 212, 191, 0.55);
|
|
81
|
+
background: linear-gradient(160deg, rgba(45, 212, 191, 0.16), rgba(45, 212, 191, 0.03));
|
|
82
|
+
color: var(--acc);
|
|
83
|
+
font: 700 13px/1 var(--mono);
|
|
84
|
+
letter-spacing: 0.04em;
|
|
85
|
+
}
|
|
86
|
+
.brand h1 {
|
|
87
|
+
margin: 0;
|
|
88
|
+
font-size: 17px; font-weight: 700; letter-spacing: 0.14em;
|
|
89
|
+
text-transform: uppercase; color: var(--ink);
|
|
90
|
+
}
|
|
91
|
+
.brand h1 em { font-style: normal; color: var(--acc); }
|
|
92
|
+
.brand p { margin: 2px 0 0; font-size: 12px; color: #7c8aa0; }
|
|
93
|
+
.meta { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
|
94
|
+
.pill {
|
|
95
|
+
display: inline-flex; align-items: center; gap: 7px;
|
|
96
|
+
padding: 5px 11px;
|
|
97
|
+
border: 1px solid var(--line-2);
|
|
98
|
+
border-radius: 999px;
|
|
99
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.05em;
|
|
100
|
+
text-transform: uppercase; color: var(--dim);
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
}
|
|
103
|
+
.pill .dot {
|
|
104
|
+
width: 7px; height: 7px; border-radius: 50%;
|
|
105
|
+
background: var(--ok);
|
|
106
|
+
box-shadow: 0 0 6px rgba(74, 222, 128, 0.8);
|
|
107
|
+
}
|
|
108
|
+
.pill.secure .dot { background: var(--acc); box-shadow: 0 0 6px rgba(45, 212, 191, 0.8); }
|
|
109
|
+
|
|
110
|
+
/* ---- cards ------------------------------------------------------------ */
|
|
111
|
+
main.wrap { padding-top: 22px; display: grid; gap: 16px; }
|
|
112
|
+
.card {
|
|
113
|
+
border: 1px solid var(--line);
|
|
114
|
+
border-radius: var(--radius);
|
|
115
|
+
background: linear-gradient(180deg, var(--card-2), var(--card));
|
|
116
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
|
117
|
+
padding: 20px 22px 22px;
|
|
118
|
+
}
|
|
119
|
+
.card-head { display: flex; align-items: baseline; gap: 12px; margin: 0 0 14px; }
|
|
120
|
+
.card-head h2 {
|
|
121
|
+
margin: 0;
|
|
122
|
+
font-size: 13px; font-weight: 700; letter-spacing: 0.12em;
|
|
123
|
+
text-transform: uppercase; color: var(--mut);
|
|
124
|
+
}
|
|
125
|
+
.card-head .hint { margin: 0; font-size: 12px; color: #7c8aa0; }
|
|
126
|
+
|
|
127
|
+
/* ---- forms ------------------------------------------------------------ */
|
|
128
|
+
label {
|
|
129
|
+
display: block; margin: 0 0 6px;
|
|
130
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.07em;
|
|
131
|
+
text-transform: uppercase; color: var(--mut);
|
|
132
|
+
}
|
|
133
|
+
input, select {
|
|
134
|
+
width: 100%;
|
|
135
|
+
height: 38px;
|
|
136
|
+
padding: 0 11px;
|
|
137
|
+
border: 1px solid var(--line-2);
|
|
138
|
+
border-radius: 8px;
|
|
139
|
+
background: #0b0e13;
|
|
140
|
+
color: var(--ink);
|
|
141
|
+
font: 500 13px/1 var(--sans);
|
|
142
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
143
|
+
}
|
|
144
|
+
.mono input, input.mono { font-family: var(--mono); }
|
|
145
|
+
input:focus, select:focus {
|
|
146
|
+
outline: none;
|
|
147
|
+
border-color: var(--acc);
|
|
148
|
+
box-shadow: 0 0 0 3px rgba(45, 212, 191, 0.16);
|
|
149
|
+
}
|
|
150
|
+
input::placeholder { color: #46536b; }
|
|
151
|
+
.selwrap { position: relative; }
|
|
152
|
+
.selwrap select { appearance: none; padding-right: 32px; cursor: pointer; }
|
|
153
|
+
.selwrap::after {
|
|
154
|
+
content: "";
|
|
155
|
+
position: absolute; right: 13px; top: 50%;
|
|
156
|
+
width: 7px; height: 7px;
|
|
157
|
+
border-right: 1.5px solid var(--dim);
|
|
158
|
+
border-bottom: 1.5px solid var(--dim);
|
|
159
|
+
transform: translateY(-70%) rotate(45deg);
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ---- hero ------------------------------------------------------------- */
|
|
164
|
+
.step-tag {
|
|
165
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
166
|
+
margin: 0 0 10px;
|
|
167
|
+
font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
|
|
168
|
+
text-transform: uppercase; color: var(--acc);
|
|
169
|
+
}
|
|
170
|
+
.step-tag .n {
|
|
171
|
+
width: 18px; height: 18px; border-radius: 50%;
|
|
172
|
+
display: grid; place-items: center;
|
|
173
|
+
border: 1px solid rgba(45, 212, 191, 0.5);
|
|
174
|
+
font: 700 10px/1 var(--mono);
|
|
175
|
+
}
|
|
176
|
+
.hero-row { display: flex; gap: 12px; align-items: stretch; }
|
|
177
|
+
.hero-row .grow { flex: 1; }
|
|
178
|
+
#path { height: 48px; font-size: 14px; }
|
|
179
|
+
.hero-row kbd {
|
|
180
|
+
display: inline-block; margin-left: 8px;
|
|
181
|
+
padding: 2px 6px;
|
|
182
|
+
border: 1px solid var(--line-2); border-bottom-width: 2px;
|
|
183
|
+
border-radius: 5px; background: #0b0e13;
|
|
184
|
+
font: 600 10px/1.4 var(--mono); color: var(--dim);
|
|
185
|
+
}
|
|
186
|
+
.hero-hint { margin: 10px 0 0; font-size: 12px; color: #7c8aa0; }
|
|
187
|
+
|
|
188
|
+
/* ---- buttons ---------------------------------------------------------- */
|
|
189
|
+
button {
|
|
190
|
+
height: 40px;
|
|
191
|
+
padding: 0 16px;
|
|
192
|
+
border: 1px solid var(--line-2);
|
|
193
|
+
border-radius: 8px;
|
|
194
|
+
background: transparent;
|
|
195
|
+
color: var(--mut);
|
|
196
|
+
font: 650 12px/1 var(--sans);
|
|
197
|
+
letter-spacing: 0.08em;
|
|
198
|
+
text-transform: uppercase;
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s, transform 0.06s;
|
|
202
|
+
}
|
|
203
|
+
button:hover {
|
|
204
|
+
color: var(--acc);
|
|
205
|
+
border-color: rgba(45, 212, 191, 0.55);
|
|
206
|
+
background: var(--acc-bg);
|
|
207
|
+
}
|
|
208
|
+
button:active { transform: translateY(1px); }
|
|
209
|
+
button.primary {
|
|
210
|
+
border-color: transparent;
|
|
211
|
+
background: linear-gradient(180deg, var(--acc), var(--acc-strong));
|
|
212
|
+
color: #052e28;
|
|
213
|
+
font-weight: 750;
|
|
214
|
+
}
|
|
215
|
+
button.primary:hover { color: #052e28; filter: brightness(1.08); }
|
|
216
|
+
#run-info { height: 48px; padding: 0 22px; font-size: 13px; }
|
|
217
|
+
|
|
218
|
+
/* ---- explore tools ---------------------------------------------------- */
|
|
219
|
+
.tools {
|
|
220
|
+
display: grid; gap: 12px;
|
|
221
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
222
|
+
}
|
|
223
|
+
.tool {
|
|
224
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
225
|
+
border: 1px solid var(--line);
|
|
226
|
+
border-radius: 8px;
|
|
227
|
+
background: #0d1118;
|
|
228
|
+
padding: 14px 14px 12px;
|
|
229
|
+
}
|
|
230
|
+
.tool-top { display: flex; align-items: center; gap: 10px; }
|
|
231
|
+
.glyph {
|
|
232
|
+
width: 30px; height: 30px; flex: none;
|
|
233
|
+
display: grid; place-items: center;
|
|
234
|
+
border-radius: 7px;
|
|
235
|
+
border: 1px solid var(--line-2);
|
|
236
|
+
background: #10151d;
|
|
237
|
+
color: var(--acc);
|
|
238
|
+
font: 700 11px/1 var(--mono);
|
|
239
|
+
letter-spacing: 0.02em;
|
|
240
|
+
}
|
|
241
|
+
.tool-top b { font-size: 14px; font-weight: 650; color: var(--ink); }
|
|
242
|
+
.tool .desc { margin: 0; font-size: 12.5px; color: var(--mut); line-height: 1.45; }
|
|
243
|
+
.tool-opts { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; }
|
|
244
|
+
.tool-opts .full { grid-column: 1 / -1; }
|
|
245
|
+
.tool-opts label { font-size: 10px; margin: 0; color: var(--dim); }
|
|
246
|
+
.tool-opts input, .tool-opts select { height: 32px; font-size: 12px; }
|
|
247
|
+
.tool-opts .single { grid-template-columns: 1fr; }
|
|
248
|
+
.tool button { width: 100%; height: 36px; margin-top: 2px; }
|
|
249
|
+
|
|
250
|
+
/* ---- diff (collapsed secondary task) ----------------------------------- */
|
|
251
|
+
details.diffbox { border: 1px dashed var(--line-2); border-radius: var(--radius); background: rgba(13, 17, 24, 0.6); }
|
|
252
|
+
details.diffbox > summary {
|
|
253
|
+
cursor: pointer; padding: 14px 18px;
|
|
254
|
+
font-size: 13px; font-weight: 700; letter-spacing: 0.1em;
|
|
255
|
+
text-transform: uppercase; color: var(--mut);
|
|
256
|
+
user-select: none; list-style: none;
|
|
257
|
+
display: flex; align-items: center; gap: 10px;
|
|
258
|
+
}
|
|
259
|
+
details.diffbox > summary::before {
|
|
260
|
+
content: "";
|
|
261
|
+
width: 0; height: 0;
|
|
262
|
+
border-left: 5px solid var(--dim);
|
|
263
|
+
border-top: 4px solid transparent; border-bottom: 4px solid transparent;
|
|
264
|
+
transition: transform 0.15s;
|
|
265
|
+
}
|
|
266
|
+
details.diffbox[open] > summary::before { transform: rotate(90deg); }
|
|
267
|
+
details.diffbox > summary:hover { color: var(--ink); }
|
|
268
|
+
.diffbox-body { padding: 4px 18px 18px; }
|
|
269
|
+
.diffgrid { display: grid; gap: 12px; grid-template-columns: 1fr 1fr; }
|
|
270
|
+
.diffgrid + .diffgrid { margin-top: 12px; }
|
|
271
|
+
|
|
272
|
+
/* ---- result ----------------------------------------------------------- */
|
|
273
|
+
.card.result { display: grid; gap: 0; }
|
|
274
|
+
.result-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
|
|
275
|
+
.result-head h2 {
|
|
276
|
+
margin: 0;
|
|
277
|
+
font-size: 13px; font-weight: 700; letter-spacing: 0.12em;
|
|
278
|
+
text-transform: uppercase; color: var(--mut);
|
|
279
|
+
}
|
|
280
|
+
.status {
|
|
281
|
+
font: 600 11px/1 var(--mono);
|
|
282
|
+
color: var(--dim);
|
|
283
|
+
padding: 5px 10px;
|
|
284
|
+
border: 1px solid var(--line);
|
|
285
|
+
border-radius: 999px;
|
|
286
|
+
background: #0b0e13;
|
|
287
|
+
letter-spacing: 0.02em;
|
|
288
|
+
text-transform: none;
|
|
289
|
+
}
|
|
290
|
+
.status.error { color: var(--err); border-color: rgba(248, 113, 113, 0.4); }
|
|
291
|
+
|
|
292
|
+
#empty { padding: 26px 8px 10px; text-align: center; }
|
|
293
|
+
#empty .big { margin: 0 0 6px; font-size: 16px; font-weight: 650; color: var(--mut); }
|
|
294
|
+
#empty p { margin: 0 auto; max-width: 520px; font-size: 13px; color: var(--dim); }
|
|
295
|
+
|
|
296
|
+
#summary { display: grid; gap: 14px; }
|
|
297
|
+
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
298
|
+
.chip {
|
|
299
|
+
display: inline-flex; align-items: baseline; gap: 7px;
|
|
300
|
+
padding: 7px 12px;
|
|
301
|
+
border: 1px solid var(--line-2);
|
|
302
|
+
border-radius: 8px;
|
|
303
|
+
background: #0d1118;
|
|
304
|
+
}
|
|
305
|
+
.chip .k {
|
|
306
|
+
font-size: 10px; font-weight: 700; letter-spacing: 0.08em;
|
|
307
|
+
text-transform: uppercase; color: var(--dim);
|
|
308
|
+
}
|
|
309
|
+
.chip .v { font: 600 12.5px/1 var(--mono); color: var(--ink); }
|
|
310
|
+
.chip.acc .v { color: var(--acc); }
|
|
311
|
+
.kv {
|
|
312
|
+
display: grid; gap: 0;
|
|
313
|
+
border: 1px solid var(--line);
|
|
314
|
+
border-radius: 8px;
|
|
315
|
+
background: #0b0e13;
|
|
316
|
+
overflow: hidden;
|
|
317
|
+
}
|
|
318
|
+
.kv-row {
|
|
319
|
+
display: grid; grid-template-columns: 170px 1fr;
|
|
320
|
+
border-bottom: 1px solid var(--line);
|
|
321
|
+
}
|
|
322
|
+
.kv-row:last-child { border-bottom: none; }
|
|
323
|
+
.kv-row .k {
|
|
324
|
+
padding: 9px 14px;
|
|
325
|
+
font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
|
|
326
|
+
text-transform: uppercase; color: var(--dim);
|
|
327
|
+
background: rgba(22, 27, 37, 0.5);
|
|
328
|
+
}
|
|
329
|
+
.kv-row .v { padding: 9px 14px; font: 500 12.5px/1.5 var(--mono); color: #c3cedd; word-break: break-all; }
|
|
330
|
+
.kv-note { margin: 0; font-size: 12px; color: var(--dim); }
|
|
331
|
+
.kv-note b { color: var(--mut); font-weight: 650; }
|
|
332
|
+
.kv-note.error { color: var(--err); }
|
|
333
|
+
|
|
334
|
+
.table-wrap, .session-wrap {
|
|
335
|
+
overflow: auto;
|
|
336
|
+
max-height: 56vh;
|
|
337
|
+
border: 1px solid var(--line);
|
|
338
|
+
border-radius: 8px;
|
|
339
|
+
background: #0b0e13;
|
|
340
|
+
}
|
|
341
|
+
table#tbl, table#session-tbl { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
|
342
|
+
table#tbl th, table#tbl td,
|
|
343
|
+
table#session-tbl th, table#session-tbl td {
|
|
344
|
+
padding: 8px 12px;
|
|
345
|
+
border-bottom: 1px solid var(--line);
|
|
346
|
+
text-align: left; vertical-align: top;
|
|
347
|
+
font-family: var(--mono);
|
|
348
|
+
}
|
|
349
|
+
table#tbl tbody tr:last-child td,
|
|
350
|
+
table#session-tbl tbody tr:last-child td { border-bottom: none; }
|
|
351
|
+
table#tbl tbody tr:hover td,
|
|
352
|
+
table#session-tbl tbody tr:hover td { background: rgba(45, 212, 191, 0.04); }
|
|
353
|
+
table#tbl th, table#session-tbl th {
|
|
354
|
+
position: sticky; top: 0; z-index: 1;
|
|
355
|
+
color: var(--dim);
|
|
356
|
+
font: 700 10px/1.2 var(--sans);
|
|
357
|
+
letter-spacing: 0.08em;
|
|
358
|
+
text-transform: uppercase;
|
|
359
|
+
background: var(--card-2);
|
|
360
|
+
border-bottom: 1px solid var(--line-2);
|
|
361
|
+
}
|
|
362
|
+
td.conf { font: 600 10px/1 var(--sans); letter-spacing: 0.06em; text-transform: uppercase; }
|
|
363
|
+
td.conf::before {
|
|
364
|
+
content: "";
|
|
365
|
+
display: inline-block;
|
|
366
|
+
width: 7px; height: 7px; margin-right: 6px;
|
|
367
|
+
border-radius: 50%;
|
|
368
|
+
vertical-align: baseline;
|
|
369
|
+
}
|
|
370
|
+
td.conf-high { color: var(--ok); }
|
|
371
|
+
td.conf-high::before { background: var(--ok); box-shadow: 0 0 5px rgba(74, 222, 128, 0.7); }
|
|
372
|
+
td.conf-medium { color: var(--warn); }
|
|
373
|
+
td.conf-medium::before { background: var(--warn); box-shadow: 0 0 5px rgba(251, 191, 36, 0.6); }
|
|
374
|
+
td.conf-low { color: var(--err); }
|
|
375
|
+
td.conf-low::before { background: var(--err); box-shadow: 0 0 5px rgba(248, 113, 113, 0.6); }
|
|
376
|
+
|
|
377
|
+
#codeview {
|
|
378
|
+
border: 1px solid var(--line);
|
|
379
|
+
border-radius: 8px;
|
|
380
|
+
background: #0b0e13;
|
|
381
|
+
overflow: auto;
|
|
382
|
+
max-height: 60vh;
|
|
383
|
+
}
|
|
384
|
+
pre#codepre {
|
|
385
|
+
margin: 0;
|
|
386
|
+
padding: 16px 18px;
|
|
387
|
+
font: 400 12.5px/1.6 var(--mono);
|
|
388
|
+
color: #cfe6dc;
|
|
389
|
+
white-space: pre;
|
|
390
|
+
}
|
|
391
|
+
details.raw { margin-top: 12px; border: 1px solid var(--line); border-radius: 8px; background: #0b0e13; }
|
|
392
|
+
details.raw summary {
|
|
393
|
+
cursor: pointer; padding: 9px 14px;
|
|
394
|
+
font-size: 11px; font-weight: 700; letter-spacing: 0.1em;
|
|
395
|
+
text-transform: uppercase; color: var(--dim);
|
|
396
|
+
user-select: none; list-style: none;
|
|
397
|
+
display: flex; align-items: center; gap: 8px;
|
|
398
|
+
}
|
|
399
|
+
details.raw summary::before {
|
|
400
|
+
content: "";
|
|
401
|
+
width: 0; height: 0;
|
|
402
|
+
border-left: 5px solid var(--dim);
|
|
403
|
+
border-top: 4px solid transparent; border-bottom: 4px solid transparent;
|
|
404
|
+
transition: transform 0.15s;
|
|
405
|
+
}
|
|
406
|
+
details.raw[open] summary::before { transform: rotate(90deg); }
|
|
407
|
+
details.raw summary:hover { color: var(--mut); }
|
|
408
|
+
pre#out {
|
|
409
|
+
margin: 0; padding: 14px;
|
|
410
|
+
max-height: 340px; overflow: auto;
|
|
411
|
+
border-top: 1px solid var(--line);
|
|
412
|
+
font: 400 12px/1.55 var(--mono);
|
|
413
|
+
color: #b9c5d6;
|
|
414
|
+
white-space: pre-wrap; word-break: break-word;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* ---- responsive -------------------------------------------------------- */
|
|
418
|
+
@media (max-width: 760px) {
|
|
419
|
+
.topbar .wrap { flex-wrap: wrap; }
|
|
420
|
+
.meta { margin-left: 0; }
|
|
421
|
+
.hero-row { flex-direction: column; }
|
|
422
|
+
.diffgrid { grid-template-columns: 1fr; }
|
|
423
|
+
}
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
_BODY = """
|
|
427
|
+
<div class="topbar">
|
|
428
|
+
<div class="wrap">
|
|
429
|
+
<div class="mark">CV</div>
|
|
430
|
+
<div class="brand">
|
|
431
|
+
<h1>code<em>vanta</em></h1>
|
|
432
|
+
<p>Look inside any binary — safely, offline, step by step</p>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="meta">
|
|
435
|
+
<span class="pill"><span class="dot"></span>read-only</span>
|
|
436
|
+
<span class="pill secure"><span class="dot"></span>local · static</span>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
<main class="wrap">
|
|
441
|
+
|
|
442
|
+
<section class="card">
|
|
443
|
+
<p class="step-tag"><span class="n">1</span> Pick a file</p>
|
|
444
|
+
<div class="hero-row">
|
|
445
|
+
<div class="grow mono">
|
|
446
|
+
<label for="path">Binary path <kbd>Enter</kbd></label>
|
|
447
|
+
<input id="path" type="text" spellcheck="false" autocomplete="off"
|
|
448
|
+
placeholder="absolute path — e.g. a .exe, .dll, .so or Mach-O file">
|
|
449
|
+
</div>
|
|
450
|
+
<div>
|
|
451
|
+
<label style="visibility:hidden">run</label>
|
|
452
|
+
<button type="button" id="run-info" class="primary" data-action="info">Analyze ›</button>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
<p class="hero-hint">
|
|
456
|
+
The file is only read from disk — never executed, never modified, never uploaded.
|
|
457
|
+
Start with <b>Analyze</b>: you get a plain-language overview, then the views below dig deeper.
|
|
458
|
+
</p>
|
|
459
|
+
</section>
|
|
460
|
+
|
|
461
|
+
<section class="card result">
|
|
462
|
+
<div class="result-head">
|
|
463
|
+
<h2>Result</h2>
|
|
464
|
+
<span class="status" id="status">idle</span>
|
|
465
|
+
</div>
|
|
466
|
+
<div id="empty">
|
|
467
|
+
<p class="big">Nothing loaded yet</p>
|
|
468
|
+
<p>Type the path to a program above and press <b>Analyze</b> (or Enter).
|
|
469
|
+
You will see what the file is made of — then each view below answers one question about it.</p>
|
|
470
|
+
</div>
|
|
471
|
+
<div id="summary" class="hidden"></div>
|
|
472
|
+
<div id="codeview" class="hidden"><pre id="codepre"></pre></div>
|
|
473
|
+
<div class="table-wrap hidden">
|
|
474
|
+
<table id="tbl">
|
|
475
|
+
<thead><tr></tr></thead>
|
|
476
|
+
<tbody></tbody>
|
|
477
|
+
</table>
|
|
478
|
+
</div>
|
|
479
|
+
<details class="raw">
|
|
480
|
+
<summary>Raw JSON (for scripts and agents)</summary>
|
|
481
|
+
<pre id="out">No request yet.</pre>
|
|
482
|
+
</details>
|
|
483
|
+
</section>
|
|
484
|
+
|
|
485
|
+
<section class="card">
|
|
486
|
+
<p class="step-tag"><span class="n">2</span> Dig deeper — one question per view</p>
|
|
487
|
+
<div class="tools">
|
|
488
|
+
|
|
489
|
+
<div class="tool">
|
|
490
|
+
<div class="tool-top"><span class="glyph">fn</span><b>Functions</b></div>
|
|
491
|
+
<p class="desc">What code lives here? Lists candidate functions and how confident we are about each.</p>
|
|
492
|
+
<div class="tool-opts single">
|
|
493
|
+
<div>
|
|
494
|
+
<label for="fn-limit">Max rows</label>
|
|
495
|
+
<input id="fn-limit" type="number" min="1" max="200000" step="1" value="100">
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
<button type="button" data-action="functions">Show functions</button>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<div class="tool">
|
|
502
|
+
<div class="tool-top"><span class="glyph">tx</span><b>Text inside</b></div>
|
|
503
|
+
<p class="desc">Readable strings hidden in the file — names, messages, URLs, paths.</p>
|
|
504
|
+
<div class="tool-opts">
|
|
505
|
+
<div>
|
|
506
|
+
<label for="st-min">Shortest string</label>
|
|
507
|
+
<input id="st-min" type="number" min="1" max="200000" step="1" value="4">
|
|
508
|
+
</div>
|
|
509
|
+
<div>
|
|
510
|
+
<label for="st-limit">Max rows</label>
|
|
511
|
+
<input id="st-limit" type="number" min="1" max="200000" step="1" value="100">
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
<button type="button" data-action="strings">Show strings</button>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<div class="tool">
|
|
518
|
+
<div class="tool-top"><span class="glyph">xr</span><b>Who calls whom</b></div>
|
|
519
|
+
<p class="desc">Cross-references: every direct call, jump and branch between addresses.</p>
|
|
520
|
+
<div class="tool-opts single">
|
|
521
|
+
<div>
|
|
522
|
+
<label for="xr-limit">Max rows</label>
|
|
523
|
+
<input id="xr-limit" type="number" min="1" max="200000" step="1" value="100">
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
<button type="button" data-action="xrefs">Show xrefs</button>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
<div class="tool">
|
|
530
|
+
<div class="tool-top"><span class="glyph">cf</span><b>Code flow</b></div>
|
|
531
|
+
<p class="desc">A control-flow map of one function — the blocks it runs and where it branches.</p>
|
|
532
|
+
<div class="tool-opts">
|
|
533
|
+
<div class="full mono">
|
|
534
|
+
<label for="cfg-address">Start address</label>
|
|
535
|
+
<input id="cfg-address" type="text" spellcheck="false" autocomplete="off" value="0x401000">
|
|
536
|
+
</div>
|
|
537
|
+
<div class="full">
|
|
538
|
+
<label for="cfg-traversal" title="linear walks a whole region; recursive follows only reachable code">Style</label>
|
|
539
|
+
<div class="selwrap">
|
|
540
|
+
<select id="cfg-traversal">
|
|
541
|
+
<option value="linear">linear — whole region</option>
|
|
542
|
+
<option value="recursive">recursive — follow the flow</option>
|
|
543
|
+
</select>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
<button type="button" data-action="cfg">Show code flow</button>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<div class="tool">
|
|
551
|
+
<div class="tool-top"><span class="glyph">st</span><b>Data layouts</b></div>
|
|
552
|
+
<p class="desc">Guesses the struct/layout of data at an address, from how the code reads and writes it.</p>
|
|
553
|
+
<div class="tool-opts">
|
|
554
|
+
<div class="full mono">
|
|
555
|
+
<label for="su-address">Address</label>
|
|
556
|
+
<input id="su-address" type="text" spellcheck="false" autocomplete="off" value="0x401000">
|
|
557
|
+
</div>
|
|
558
|
+
<div>
|
|
559
|
+
<label for="su-max">Max fields</label>
|
|
560
|
+
<input id="su-max" type="number" min="1" max="512" step="1" value="64">
|
|
561
|
+
</div>
|
|
562
|
+
<div>
|
|
563
|
+
<label for="su-stack">Stack vars</label>
|
|
564
|
+
<div class="selwrap">
|
|
565
|
+
<select id="su-stack">
|
|
566
|
+
<option value="0">exclude</option>
|
|
567
|
+
<option value="1">include</option>
|
|
568
|
+
</select>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
</div>
|
|
572
|
+
<button type="button" data-action="struct">Show layout</button>
|
|
573
|
+
</div>
|
|
574
|
+
|
|
575
|
+
<div class="tool">
|
|
576
|
+
<div class="tool-top"><span class="glyph">dc</span><b>Read as C</b></div>
|
|
577
|
+
<p class="desc">Turns the function at an address back into readable C-like pseudocode — a heuristic sketch, not the original source.</p>
|
|
578
|
+
<div class="tool-opts">
|
|
579
|
+
<div class="full mono">
|
|
580
|
+
<label for="dc-address">Function address</label>
|
|
581
|
+
<input id="dc-address" type="text" spellcheck="false" autocomplete="off" value="0x401000">
|
|
582
|
+
</div>
|
|
583
|
+
<div class="full">
|
|
584
|
+
<label for="backend" title="auto picks the best engine installed; built-in always works">Engine</label>
|
|
585
|
+
<div class="selwrap">
|
|
586
|
+
<select id="backend">
|
|
587
|
+
<option value="auto">auto</option>
|
|
588
|
+
<option value="native">built-in</option>
|
|
589
|
+
<option value="angr">angr</option>
|
|
590
|
+
<option value="ghidra">ghidra</option>
|
|
591
|
+
</select>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
<button type="button" data-action="decompile">Decompile</button>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<div class="tool">
|
|
599
|
+
<div class="tool-top"><span class="glyph">cg</span><b>Call map</b></div>
|
|
600
|
+
<p class="desc">Which functions call which — the program's overall structure at a glance.</p>
|
|
601
|
+
<div class="tool-opts">
|
|
602
|
+
<div>
|
|
603
|
+
<label for="cg-limit">Max functions</label>
|
|
604
|
+
<input id="cg-limit" type="number" min="1" max="200000" step="1" value="100">
|
|
605
|
+
</div>
|
|
606
|
+
<div>
|
|
607
|
+
<label for="cg-edges">Max edges</label>
|
|
608
|
+
<input id="cg-edges" type="number" min="1" max="200000" step="1" value="5000">
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
<button type="button" data-action="callgraph">Show call map</button>
|
|
612
|
+
</div>
|
|
613
|
+
|
|
614
|
+
<div class="tool">
|
|
615
|
+
<div class="tool-top"><span class="glyph">ex</span><b>Emulate</b></div>
|
|
616
|
+
<p class="desc">Runs the function at an address on a sandboxed virtual CPU — nothing is executed on your machine — and reports where it stopped, its registers and its return value.</p>
|
|
617
|
+
<div class="tool-opts">
|
|
618
|
+
<div class="full mono">
|
|
619
|
+
<label for="emu-address">Function address</label>
|
|
620
|
+
<input id="emu-address" type="text" spellcheck="false" autocomplete="off" value="0x401000">
|
|
621
|
+
</div>
|
|
622
|
+
<div class="full">
|
|
623
|
+
<label for="emu-max" title="instruction budget for one run">Max instructions</label>
|
|
624
|
+
<input id="emu-max" type="number" min="1" max="100000" step="1" value="2000">
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
<button type="button" id="emu-run" data-action="emulate">Emulate function</button>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<div class="tool">
|
|
631
|
+
<div class="tool-top"><span class="glyph">pa</span><b>Paths</b></div>
|
|
632
|
+
<p class="desc">Explores the branch decisions of one function and lists every path it can take, with the register state each path ends in.</p>
|
|
633
|
+
<div class="tool-opts">
|
|
634
|
+
<div class="full mono">
|
|
635
|
+
<label for="sym-address">Function address</label>
|
|
636
|
+
<input id="sym-address" type="text" spellcheck="false" autocomplete="off" value="0x401000">
|
|
637
|
+
</div>
|
|
638
|
+
<div>
|
|
639
|
+
<label for="sym-max-paths" title="how many paths the explorer may report">Max paths</label>
|
|
640
|
+
<input id="sym-max-paths" type="number" min="1" max="4096" step="1" value="64">
|
|
641
|
+
</div>
|
|
642
|
+
<div>
|
|
643
|
+
<label for="sym-max-steps" title="instruction budget for the whole exploration">Max steps</label>
|
|
644
|
+
<input id="sym-max-steps" type="number" min="1" max="200000" step="1" value="2000">
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
<button type="button" id="sym-run" data-action="symbolic">Explore paths</button>
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
</div>
|
|
651
|
+
</section>
|
|
652
|
+
|
|
653
|
+
<details class="diffbox">
|
|
654
|
+
<summary>Advanced — compare two versions of the same program</summary>
|
|
655
|
+
<div class="diffbox-body">
|
|
656
|
+
<p class="kv-note" style="margin:0 0 12px">
|
|
657
|
+
Give a baseline and a candidate build; CODEVANTA pairs up functions and shows
|
|
658
|
+
what changed, was added, or disappeared.
|
|
659
|
+
</p>
|
|
660
|
+
<div class="diffgrid">
|
|
661
|
+
<div class="mono">
|
|
662
|
+
<label for="left">Baseline (old)</label>
|
|
663
|
+
<input id="left" type="text" spellcheck="false" autocomplete="off" placeholder="absolute path">
|
|
664
|
+
</div>
|
|
665
|
+
<div class="mono">
|
|
666
|
+
<label for="right">Candidate (new)</label>
|
|
667
|
+
<input id="right" type="text" spellcheck="false" autocomplete="off" placeholder="absolute path">
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
<div class="diffgrid" style="grid-template-columns: 1fr 1fr; max-width:520px">
|
|
671
|
+
<div>
|
|
672
|
+
<label for="semantic">Matching</label>
|
|
673
|
+
<div class="selwrap">
|
|
674
|
+
<select id="semantic">
|
|
675
|
+
<option value="0">structural</option>
|
|
676
|
+
<option value="1">semantic (fuzzy)</option>
|
|
677
|
+
</select>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
<div>
|
|
681
|
+
<label for="threshold" title="0 pairs everything, 1 demands identical shapes">Strictness</label>
|
|
682
|
+
<input id="threshold" type="number" min="0" max="1" step="0.05" value="0.5">
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
<div style="max-width:220px; margin-top:14px">
|
|
686
|
+
<button type="button" data-action="diff" class="primary" style="width:100%">Compare</button>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
</details>
|
|
690
|
+
|
|
691
|
+
<section class="card" id="sessions">
|
|
692
|
+
<p class="step-tag"><span class="n">3</span> Sessions — keep what you found</p>
|
|
693
|
+
<p class="hero-hint" style="margin-top:0">
|
|
694
|
+
Saving stores the file above as a session record, so the list is still here after a
|
|
695
|
+
reload, a restart or in another tab. Records live in the local session database and
|
|
696
|
+
the file itself is never modified.
|
|
697
|
+
</p>
|
|
698
|
+
<div class="hero-row">
|
|
699
|
+
<div class="grow mono">
|
|
700
|
+
<label for="session-notes">Note for this session (optional)</label>
|
|
701
|
+
<input id="session-notes" type="text" spellcheck="false" autocomplete="off"
|
|
702
|
+
placeholder="e.g. candidate build, crash repro, reviewed by hand">
|
|
703
|
+
</div>
|
|
704
|
+
<div>
|
|
705
|
+
<label style="visibility:hidden">save</label>
|
|
706
|
+
<button type="button" id="session-save" class="primary" data-action="session_save">Save session</button>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
<div class="hero-row" style="margin-top:14px">
|
|
710
|
+
<div style="max-width:190px">
|
|
711
|
+
<label for="session-limit" title="how many stored sessions the list asks for">Rows to list</label>
|
|
712
|
+
<input id="session-limit" type="number" min="1" max="10000" step="1" value="50">
|
|
713
|
+
</div>
|
|
714
|
+
<div>
|
|
715
|
+
<label style="visibility:hidden">refresh</label>
|
|
716
|
+
<button type="button" id="session-refresh" data-action="session_list">Refresh list</button>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
<div class="session-wrap hidden" id="session-wrap" style="margin-top:14px">
|
|
720
|
+
<table id="session-tbl">
|
|
721
|
+
<thead><tr></tr></thead>
|
|
722
|
+
<tbody></tbody>
|
|
723
|
+
</table>
|
|
724
|
+
</div>
|
|
725
|
+
<p class="kv-note" id="session-note" style="margin-top:10px">No sessions loaded yet.</p>
|
|
726
|
+
</section>
|
|
727
|
+
|
|
728
|
+
</main>
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
_SCRIPT = """
|
|
733
|
+
(function () {
|
|
734
|
+
"use strict";
|
|
735
|
+
|
|
736
|
+
var ENDPOINTS = {
|
|
737
|
+
info: "/api/info",
|
|
738
|
+
functions: "/api/functions",
|
|
739
|
+
strings: "/api/strings",
|
|
740
|
+
xrefs: "/api/xrefs",
|
|
741
|
+
cfg: "/api/cfg",
|
|
742
|
+
struct: "/api/struct",
|
|
743
|
+
callgraph: "/api/callgraph",
|
|
744
|
+
diff: "/api/diff",
|
|
745
|
+
decompile: "/api/decompile",
|
|
746
|
+
emulate: "/api/emulate",
|
|
747
|
+
symbolic: "/api/symbolic",
|
|
748
|
+
session_save: "/api/session_save",
|
|
749
|
+
session_list: "/api/session_list"
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
var MAX_TABLE_ROWS = 200;
|
|
753
|
+
var MAX_TABLE_COLUMNS = 8;
|
|
754
|
+
var MAX_CELL_CHARS = 120;
|
|
755
|
+
|
|
756
|
+
function byId(id) {
|
|
757
|
+
return document.getElementById(id);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function fieldValue(id) {
|
|
761
|
+
var element = byId(id);
|
|
762
|
+
if (!element || typeof element.value !== "string") {
|
|
763
|
+
return "";
|
|
764
|
+
}
|
|
765
|
+
return element.value.trim();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function setStatus(message, isError) {
|
|
769
|
+
var element = byId("status");
|
|
770
|
+
if (!element) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
element.textContent = message;
|
|
774
|
+
element.className = isError ? "status error" : "status";
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function showView(name) {
|
|
778
|
+
var empty = byId("empty");
|
|
779
|
+
var summary = byId("summary");
|
|
780
|
+
var codeview = byId("codeview");
|
|
781
|
+
var tableWrap = document.querySelector(".table-wrap");
|
|
782
|
+
if (empty) {
|
|
783
|
+
empty.className = name === "empty" ? "" : "hidden";
|
|
784
|
+
}
|
|
785
|
+
if (summary) {
|
|
786
|
+
summary.className = name === "summary" ? "" : "hidden";
|
|
787
|
+
}
|
|
788
|
+
if (codeview) {
|
|
789
|
+
codeview.className = name === "code" ? "" : "hidden";
|
|
790
|
+
}
|
|
791
|
+
if (tableWrap) {
|
|
792
|
+
tableWrap.className = name === "table" ? "table-wrap" : "table-wrap hidden";
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function addParam(parts, key, value) {
|
|
797
|
+
if (value === null || value === undefined || value === "") {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function queryFor(action) {
|
|
804
|
+
var parts = [];
|
|
805
|
+
if (action === "diff") {
|
|
806
|
+
addParam(parts, "left", fieldValue("left"));
|
|
807
|
+
addParam(parts, "right", fieldValue("right"));
|
|
808
|
+
addParam(parts, "semantic", fieldValue("semantic") || "0");
|
|
809
|
+
addParam(parts, "threshold", fieldValue("threshold"));
|
|
810
|
+
return parts;
|
|
811
|
+
}
|
|
812
|
+
// The session routes accept a closed set of parameters and answer anything
|
|
813
|
+
// else with 400, so they are built here instead of falling through to the
|
|
814
|
+
// generic "path" line below: the list route takes no path at all.
|
|
815
|
+
if (action === "session_save") {
|
|
816
|
+
addParam(parts, "path", fieldValue("path"));
|
|
817
|
+
addParam(parts, "notes", fieldValue("session-notes"));
|
|
818
|
+
return parts;
|
|
819
|
+
}
|
|
820
|
+
if (action === "session_list") {
|
|
821
|
+
addParam(parts, "limit", fieldValue("session-limit"));
|
|
822
|
+
return parts;
|
|
823
|
+
}
|
|
824
|
+
addParam(parts, "path", fieldValue("path"));
|
|
825
|
+
if (action === "info") {
|
|
826
|
+
addParam(parts, "max_functions", fieldValue("fn-limit"));
|
|
827
|
+
} else if (action === "functions") {
|
|
828
|
+
addParam(parts, "limit", fieldValue("fn-limit"));
|
|
829
|
+
} else if (action === "xrefs") {
|
|
830
|
+
addParam(parts, "limit", fieldValue("xr-limit"));
|
|
831
|
+
} else if (action === "strings") {
|
|
832
|
+
addParam(parts, "limit", fieldValue("st-limit"));
|
|
833
|
+
addParam(parts, "min_length", fieldValue("st-min"));
|
|
834
|
+
} else if (action === "cfg") {
|
|
835
|
+
addParam(parts, "address", fieldValue("cfg-address"));
|
|
836
|
+
addParam(parts, "traversal", fieldValue("cfg-traversal") || "linear");
|
|
837
|
+
} else if (action === "struct") {
|
|
838
|
+
addParam(parts, "address", fieldValue("su-address"));
|
|
839
|
+
addParam(parts, "include_stack", fieldValue("su-stack") || "0");
|
|
840
|
+
addParam(parts, "max_fields", fieldValue("su-max"));
|
|
841
|
+
} else if (action === "callgraph") {
|
|
842
|
+
addParam(parts, "limit_functions", fieldValue("cg-limit"));
|
|
843
|
+
addParam(parts, "limit_edges", fieldValue("cg-edges"));
|
|
844
|
+
} else if (action === "decompile") {
|
|
845
|
+
addParam(parts, "address", fieldValue("dc-address"));
|
|
846
|
+
addParam(parts, "backend", fieldValue("backend") || "auto");
|
|
847
|
+
} else if (action === "emulate") {
|
|
848
|
+
addParam(parts, "address", fieldValue("emu-address"));
|
|
849
|
+
addParam(parts, "max_instructions", fieldValue("emu-max"));
|
|
850
|
+
} else if (action === "symbolic") {
|
|
851
|
+
addParam(parts, "address", fieldValue("sym-address"));
|
|
852
|
+
addParam(parts, "max_paths", fieldValue("sym-max-paths"));
|
|
853
|
+
addParam(parts, "max_steps", fieldValue("sym-max-steps"));
|
|
854
|
+
}
|
|
855
|
+
return parts;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function isRowList(value) {
|
|
859
|
+
return Object.prototype.toString.call(value) === "[object Array]" && value.length > 0;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function pickRows(data) {
|
|
863
|
+
if (!data || typeof data !== "object") {
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
var keys = [
|
|
867
|
+
"items",
|
|
868
|
+
"functions",
|
|
869
|
+
"xrefs",
|
|
870
|
+
"strings",
|
|
871
|
+
"blocks",
|
|
872
|
+
"fields",
|
|
873
|
+
"entries",
|
|
874
|
+
"nodes",
|
|
875
|
+
"sections",
|
|
876
|
+
"array_groups",
|
|
877
|
+
"warnings"
|
|
878
|
+
];
|
|
879
|
+
var index;
|
|
880
|
+
for (index = 0; index < keys.length; index += 1) {
|
|
881
|
+
if (isRowList(data[keys[index]])) {
|
|
882
|
+
return data[keys[index]];
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return [];
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function cellText(value) {
|
|
889
|
+
if (value === null || value === undefined) {
|
|
890
|
+
return "";
|
|
891
|
+
}
|
|
892
|
+
if (typeof value === "object") {
|
|
893
|
+
var encoded;
|
|
894
|
+
try {
|
|
895
|
+
encoded = JSON.stringify(value);
|
|
896
|
+
} catch (error) {
|
|
897
|
+
encoded = String(error);
|
|
898
|
+
}
|
|
899
|
+
if (encoded.length > MAX_CELL_CHARS) {
|
|
900
|
+
return encoded.slice(0, MAX_CELL_CHARS - 3) + "...";
|
|
901
|
+
}
|
|
902
|
+
return encoded;
|
|
903
|
+
}
|
|
904
|
+
return String(value);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function columnsFor(rows) {
|
|
908
|
+
var columns = [];
|
|
909
|
+
var sample = rows.slice(0, 25);
|
|
910
|
+
var index;
|
|
911
|
+
for (index = 0; index < sample.length; index += 1) {
|
|
912
|
+
var row = sample[index];
|
|
913
|
+
if (!row || typeof row !== "object") {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
var names = Object.keys(row);
|
|
917
|
+
var position;
|
|
918
|
+
for (position = 0; position < names.length; position += 1) {
|
|
919
|
+
if (columns.length >= MAX_TABLE_COLUMNS) {
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
if (columns.indexOf(names[position]) === -1) {
|
|
923
|
+
columns.push(names[position]);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return columns;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function renderTable(rows) {
|
|
931
|
+
var table = byId("tbl");
|
|
932
|
+
if (!table || !table.tHead || !table.tBodies.length) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
var head = table.tHead.rows[0];
|
|
936
|
+
var body = table.tBodies[0];
|
|
937
|
+
while (head.cells.length > 0) {
|
|
938
|
+
head.deleteCell(-1);
|
|
939
|
+
}
|
|
940
|
+
while (body.firstChild) {
|
|
941
|
+
body.removeChild(body.firstChild);
|
|
942
|
+
}
|
|
943
|
+
if (!rows || !rows.length) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
var columns = columnsFor(rows);
|
|
947
|
+
if (!columns.length) {
|
|
948
|
+
columns = ["value"];
|
|
949
|
+
}
|
|
950
|
+
columns.forEach(function (name) {
|
|
951
|
+
var cell = document.createElement("th");
|
|
952
|
+
cell.textContent = name;
|
|
953
|
+
head.appendChild(cell);
|
|
954
|
+
});
|
|
955
|
+
rows.slice(0, MAX_TABLE_ROWS).forEach(function (row) {
|
|
956
|
+
var tr = document.createElement("tr");
|
|
957
|
+
columns.forEach(function (name) {
|
|
958
|
+
var td = document.createElement("td");
|
|
959
|
+
if (row && typeof row === "object") {
|
|
960
|
+
td.textContent = cellText(row[name]);
|
|
961
|
+
} else {
|
|
962
|
+
td.textContent = cellText(row);
|
|
963
|
+
}
|
|
964
|
+
if (name === "confidence" || name === "confidence_level") {
|
|
965
|
+
var level = String(td.textContent).toLowerCase();
|
|
966
|
+
if (level.indexOf("certain") !== -1 || level.indexOf("high") !== -1) {
|
|
967
|
+
td.className = "conf conf-high";
|
|
968
|
+
} else if (level.indexOf("medium") !== -1) {
|
|
969
|
+
td.className = "conf conf-medium";
|
|
970
|
+
} else if (level !== "") {
|
|
971
|
+
td.className = "conf conf-low";
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
tr.appendChild(td);
|
|
975
|
+
});
|
|
976
|
+
body.appendChild(tr);
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Columns of the Sessions panel, in display order. They are the fields
|
|
981
|
+
// /api/session_list returns, spelled once so the header and the cells can
|
|
982
|
+
// never drift apart.
|
|
983
|
+
var SESSION_COLUMNS = [
|
|
984
|
+
"session_id",
|
|
985
|
+
"created_at",
|
|
986
|
+
"path",
|
|
987
|
+
"format",
|
|
988
|
+
"architecture",
|
|
989
|
+
"notes"
|
|
990
|
+
];
|
|
991
|
+
|
|
992
|
+
function setSessionNote(message, isError) {
|
|
993
|
+
var element = byId("session-note");
|
|
994
|
+
if (!element) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
element.textContent = message;
|
|
998
|
+
element.className = isError ? "kv-note error" : "kv-note";
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function sessionRows(data) {
|
|
1002
|
+
if (!data || typeof data !== "object" || !isRowList(data.sessions)) {
|
|
1003
|
+
return [];
|
|
1004
|
+
}
|
|
1005
|
+
return data.sessions;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function renderSessions(data) {
|
|
1009
|
+
var table = byId("session-tbl");
|
|
1010
|
+
var wrap = byId("session-wrap");
|
|
1011
|
+
var rows = sessionRows(data);
|
|
1012
|
+
if (!table || !table.tHead || !table.tBodies.length) {
|
|
1013
|
+
return rows.length;
|
|
1014
|
+
}
|
|
1015
|
+
var head = table.tHead.rows[0];
|
|
1016
|
+
var body = table.tBodies[0];
|
|
1017
|
+
while (head.cells.length > 0) {
|
|
1018
|
+
head.deleteCell(-1);
|
|
1019
|
+
}
|
|
1020
|
+
while (body.firstChild) {
|
|
1021
|
+
body.removeChild(body.firstChild);
|
|
1022
|
+
}
|
|
1023
|
+
SESSION_COLUMNS.forEach(function (name) {
|
|
1024
|
+
var cell = document.createElement("th");
|
|
1025
|
+
cell.textContent = name;
|
|
1026
|
+
head.appendChild(cell);
|
|
1027
|
+
});
|
|
1028
|
+
rows.slice(0, MAX_TABLE_ROWS).forEach(function (row) {
|
|
1029
|
+
var tr = document.createElement("tr");
|
|
1030
|
+
SESSION_COLUMNS.forEach(function (name) {
|
|
1031
|
+
var td = document.createElement("td");
|
|
1032
|
+
// textContent only: a path or a note is attacker-controlled text and is
|
|
1033
|
+
// never parsed as markup.
|
|
1034
|
+
td.textContent = cellText(row && typeof row === "object" ? row[name] : row);
|
|
1035
|
+
tr.appendChild(td);
|
|
1036
|
+
});
|
|
1037
|
+
body.appendChild(tr);
|
|
1038
|
+
});
|
|
1039
|
+
if (wrap) {
|
|
1040
|
+
wrap.className = rows.length ? "session-wrap" : "session-wrap hidden";
|
|
1041
|
+
}
|
|
1042
|
+
return rows.length;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function loadSessions(announce) {
|
|
1046
|
+
// Used on page load and after a save: it keeps the panel current without
|
|
1047
|
+
// touching the status line or the raw-JSON box the user may be reading.
|
|
1048
|
+
// ``announce`` says whether the panel note may be rewritten -- the startup
|
|
1049
|
+
// call wants the restored count, while the refresh that follows a save must
|
|
1050
|
+
// leave the "saved <id>" confirmation in place.
|
|
1051
|
+
var parts = [];
|
|
1052
|
+
addParam(parts, "limit", fieldValue("session-limit"));
|
|
1053
|
+
var url = ENDPOINTS.session_list + (parts.length ? "?" + parts.join("&") : "");
|
|
1054
|
+
fetch(url, { headers: { Accept: "application/json" } })
|
|
1055
|
+
.then(function (response) {
|
|
1056
|
+
return response
|
|
1057
|
+
.json()
|
|
1058
|
+
.then(function (data) {
|
|
1059
|
+
return { ok: response.ok, status: response.status, data: data };
|
|
1060
|
+
})
|
|
1061
|
+
.catch(function () {
|
|
1062
|
+
return { ok: response.ok, status: response.status, data: { error: "invalid JSON" } };
|
|
1063
|
+
});
|
|
1064
|
+
})
|
|
1065
|
+
.then(function (result) {
|
|
1066
|
+
if (!result.ok) {
|
|
1067
|
+
if (announce) {
|
|
1068
|
+
var detail = result.data && result.data.error ? result.data.error : "request failed";
|
|
1069
|
+
setSessionNote("HTTP " + result.status + " " + detail, true);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
var count = renderSessions(result.data);
|
|
1074
|
+
if (announce) {
|
|
1075
|
+
if (count > 0) {
|
|
1076
|
+
setSessionNote(count + " saved session(s) restored from the session database", false);
|
|
1077
|
+
} else {
|
|
1078
|
+
setSessionNote("No sessions stored yet \u2014 pick a file and press Save session.", false);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
})
|
|
1082
|
+
.catch(function (error) {
|
|
1083
|
+
if (announce) {
|
|
1084
|
+
setSessionNote("session list failed: " + String(error), true);
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function humanSize(bytes) {
|
|
1090
|
+
var value = Number(bytes);
|
|
1091
|
+
if (!isFinite(value) || value < 0) {
|
|
1092
|
+
return String(bytes);
|
|
1093
|
+
}
|
|
1094
|
+
if (value < 1024) {
|
|
1095
|
+
return value + " B";
|
|
1096
|
+
}
|
|
1097
|
+
if (value < 1024 * 1024) {
|
|
1098
|
+
return (value / 1024).toFixed(1) + " KB";
|
|
1099
|
+
}
|
|
1100
|
+
return (value / (1024 * 1024)).toFixed(1) + " MB";
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function chipRow(container, key, value, accent) {
|
|
1104
|
+
var chip = document.createElement("span");
|
|
1105
|
+
chip.className = accent ? "chip acc" : "chip";
|
|
1106
|
+
var k = document.createElement("span");
|
|
1107
|
+
k.className = "k";
|
|
1108
|
+
k.textContent = key;
|
|
1109
|
+
var v = document.createElement("span");
|
|
1110
|
+
v.className = "v";
|
|
1111
|
+
v.textContent = value;
|
|
1112
|
+
chip.appendChild(k);
|
|
1113
|
+
chip.appendChild(v);
|
|
1114
|
+
container.appendChild(chip);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function kvRow(container, key, value) {
|
|
1118
|
+
var row = document.createElement("div");
|
|
1119
|
+
row.className = "kv-row";
|
|
1120
|
+
var k = document.createElement("div");
|
|
1121
|
+
k.className = "k";
|
|
1122
|
+
k.textContent = key;
|
|
1123
|
+
var v = document.createElement("div");
|
|
1124
|
+
v.className = "v";
|
|
1125
|
+
v.textContent = value;
|
|
1126
|
+
row.appendChild(k);
|
|
1127
|
+
row.appendChild(v);
|
|
1128
|
+
container.appendChild(row);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function renderSummary(payload) {
|
|
1132
|
+
var box = byId("summary");
|
|
1133
|
+
if (!box) {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
while (box.firstChild) {
|
|
1137
|
+
box.removeChild(box.firstChild);
|
|
1138
|
+
}
|
|
1139
|
+
var binary = payload && payload.binary && typeof payload.binary === "object" ? payload.binary : {};
|
|
1140
|
+
|
|
1141
|
+
var chips = document.createElement("div");
|
|
1142
|
+
chips.className = "chips";
|
|
1143
|
+
var archBits = [binary.format, binary.architecture, binary.bitness ? binary.bitness + "-bit" : ""]
|
|
1144
|
+
.filter(Boolean).join(" · ");
|
|
1145
|
+
chipRow(chips, "file", archBits || "unknown", true);
|
|
1146
|
+
if (binary.sections) {
|
|
1147
|
+
chipRow(chips, "sections", String(binary.sections.length));
|
|
1148
|
+
}
|
|
1149
|
+
if (binary.imports) {
|
|
1150
|
+
chipRow(chips, "imports", String(binary.imports.length));
|
|
1151
|
+
}
|
|
1152
|
+
if (binary.exports) {
|
|
1153
|
+
chipRow(chips, "exports", String(binary.exports.length));
|
|
1154
|
+
}
|
|
1155
|
+
if (binary.symbols) {
|
|
1156
|
+
chipRow(chips, "symbols", String(binary.symbols.length));
|
|
1157
|
+
}
|
|
1158
|
+
if (payload.function_count !== undefined) {
|
|
1159
|
+
chipRow(chips, "functions", String(payload.function_count));
|
|
1160
|
+
}
|
|
1161
|
+
if (payload.string_count !== undefined) {
|
|
1162
|
+
chipRow(chips, "strings", String(payload.string_count));
|
|
1163
|
+
}
|
|
1164
|
+
if (binary.is_stripped !== undefined) {
|
|
1165
|
+
chipRow(chips, "symbols?", binary.is_stripped ? "stripped" : "present");
|
|
1166
|
+
}
|
|
1167
|
+
box.appendChild(chips);
|
|
1168
|
+
|
|
1169
|
+
var kv = document.createElement("div");
|
|
1170
|
+
kv.className = "kv";
|
|
1171
|
+
if (binary.path) {
|
|
1172
|
+
kvRow(kv, "Path", binary.path);
|
|
1173
|
+
}
|
|
1174
|
+
if (binary.entry_point) {
|
|
1175
|
+
kvRow(kv, "Program start (entry point)", binary.entry_point);
|
|
1176
|
+
}
|
|
1177
|
+
if (binary.image_base) {
|
|
1178
|
+
kvRow(kv, "Image base", binary.image_base);
|
|
1179
|
+
}
|
|
1180
|
+
if (binary.file_size !== undefined) {
|
|
1181
|
+
kvRow(kv, "File size", humanSize(binary.file_size));
|
|
1182
|
+
}
|
|
1183
|
+
if (binary.sha256) {
|
|
1184
|
+
kvRow(kv, "SHA-256", binary.sha256);
|
|
1185
|
+
}
|
|
1186
|
+
if (binary.warnings && binary.warnings.length) {
|
|
1187
|
+
kvRow(kv, "Notes", binary.warnings.join(" | "));
|
|
1188
|
+
}
|
|
1189
|
+
box.appendChild(kv);
|
|
1190
|
+
|
|
1191
|
+
var note = document.createElement("p");
|
|
1192
|
+
note.className = "kv-note";
|
|
1193
|
+
var lead = document.createElement("b");
|
|
1194
|
+
lead.textContent = "Next: ";
|
|
1195
|
+
note.appendChild(lead);
|
|
1196
|
+
note.appendChild(document.createTextNode(
|
|
1197
|
+
"pick any view below to dig deeper \u2014 every result keeps its evidence and confidence level."
|
|
1198
|
+
));
|
|
1199
|
+
box.appendChild(note);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function showPayload(payload) {
|
|
1203
|
+
var output = byId("out");
|
|
1204
|
+
if (!output) {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
try {
|
|
1208
|
+
output.textContent = JSON.stringify(payload, null, 2);
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
output.textContent = String(payload);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function run(action) {
|
|
1215
|
+
var endpoint = ENDPOINTS[action];
|
|
1216
|
+
if (!endpoint) {
|
|
1217
|
+
setStatus("unknown action " + action, true);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
var parts = queryFor(action);
|
|
1221
|
+
var url = endpoint + (parts.length ? "?" + parts.join("&") : "");
|
|
1222
|
+
setStatus("requesting " + endpoint, false);
|
|
1223
|
+
fetch(url, { headers: { Accept: "application/json" } })
|
|
1224
|
+
.then(function (response) {
|
|
1225
|
+
return response
|
|
1226
|
+
.json()
|
|
1227
|
+
.then(function (data) {
|
|
1228
|
+
return { ok: response.ok, status: response.status, data: data };
|
|
1229
|
+
})
|
|
1230
|
+
.catch(function () {
|
|
1231
|
+
return { ok: response.ok, status: response.status, data: { error: "invalid JSON" } };
|
|
1232
|
+
});
|
|
1233
|
+
})
|
|
1234
|
+
.then(function (result) {
|
|
1235
|
+
showPayload(result.data);
|
|
1236
|
+
if (result.ok) {
|
|
1237
|
+
if (action === "decompile" && result.data && typeof result.data.text === "string") {
|
|
1238
|
+
var codeBox = byId("codepre");
|
|
1239
|
+
if (codeBox) {
|
|
1240
|
+
codeBox.textContent = result.data.text;
|
|
1241
|
+
}
|
|
1242
|
+
showView("code");
|
|
1243
|
+
var engineName = result.data.backend ? String(result.data.backend) : "?";
|
|
1244
|
+
var warnCount = result.data.warnings && result.data.warnings.length
|
|
1245
|
+
? " · " + result.data.warnings.length + " warning(s)"
|
|
1246
|
+
: "";
|
|
1247
|
+
setStatus("HTTP " + result.status + " · engine " + engineName + warnCount, false);
|
|
1248
|
+
} else if (action === "session_list") {
|
|
1249
|
+
var listed = renderSessions(result.data);
|
|
1250
|
+
setSessionNote(
|
|
1251
|
+
"HTTP " + result.status + " \u00b7 " + listed + " session(s) in the list",
|
|
1252
|
+
false
|
|
1253
|
+
);
|
|
1254
|
+
setStatus("HTTP " + result.status + " \u00b7 " + listed + " session(s)", false);
|
|
1255
|
+
} else if (action === "session_save") {
|
|
1256
|
+
var savedId = result.data && result.data.session_id
|
|
1257
|
+
? String(result.data.session_id)
|
|
1258
|
+
: "?";
|
|
1259
|
+
var savedAt = result.data && result.data.created_at
|
|
1260
|
+
? String(result.data.created_at)
|
|
1261
|
+
: "?";
|
|
1262
|
+
setSessionNote("saved " + savedId + " at " + savedAt, false);
|
|
1263
|
+
setStatus("HTTP " + result.status + " \u00b7 session saved", false);
|
|
1264
|
+
// Refresh the table but keep the "saved <id>" note that was just
|
|
1265
|
+
// written, which is the confirmation the user asked for.
|
|
1266
|
+
loadSessions(false);
|
|
1267
|
+
} else if (action === "info") {
|
|
1268
|
+
renderSummary(result.data);
|
|
1269
|
+
showView("summary");
|
|
1270
|
+
setStatus("HTTP " + result.status + " · overview", false);
|
|
1271
|
+
} else {
|
|
1272
|
+
var rows = pickRows(result.data);
|
|
1273
|
+
renderTable(rows);
|
|
1274
|
+
showView(rows.length ? "table" : "empty");
|
|
1275
|
+
var shown = Math.min(rows.length, MAX_TABLE_ROWS);
|
|
1276
|
+
var summary = "HTTP " + result.status + (rows.length ? " · " + shown + (rows.length > MAX_TABLE_ROWS ? " of " + rows.length : "") + " rows" : " · no rows");
|
|
1277
|
+
setStatus(summary, false);
|
|
1278
|
+
}
|
|
1279
|
+
var resultCard = document.querySelector(".card.result");
|
|
1280
|
+
// A session request reports in its own panel, so the page must not
|
|
1281
|
+
// scroll the main result card into view for it.
|
|
1282
|
+
if (resultCard && resultCard.scrollIntoView && action.indexOf("session") !== 0) {
|
|
1283
|
+
resultCard.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
1284
|
+
}
|
|
1285
|
+
} else {
|
|
1286
|
+
var detail = result.data && result.data.error ? result.data.error : "request failed";
|
|
1287
|
+
setStatus("HTTP " + result.status + " " + detail, true);
|
|
1288
|
+
renderTable([]);
|
|
1289
|
+
showView("empty");
|
|
1290
|
+
if (action.indexOf("session") === 0) {
|
|
1291
|
+
setSessionNote("HTTP " + result.status + " " + detail, true);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
})
|
|
1295
|
+
.catch(function (error) {
|
|
1296
|
+
showPayload({ error: String(error) });
|
|
1297
|
+
setStatus("request failed", true);
|
|
1298
|
+
renderTable([]);
|
|
1299
|
+
showView("empty");
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
var buttons = document.querySelectorAll("button[data-action]");
|
|
1304
|
+
Array.prototype.forEach.call(buttons, function (button) {
|
|
1305
|
+
button.addEventListener("click", function (event) {
|
|
1306
|
+
run(event.currentTarget.getAttribute("data-action"));
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
var pathInput = byId("path");
|
|
1311
|
+
if (pathInput) {
|
|
1312
|
+
pathInput.addEventListener("keydown", function (event) {
|
|
1313
|
+
if (event.key === "Enter") {
|
|
1314
|
+
run("info");
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Reload the stored history as soon as the page opens: this is what makes a
|
|
1320
|
+
// session survive a reload. It leaves the "idle" status line and the empty
|
|
1321
|
+
// raw-JSON box alone and reports the restored count in the panel note.
|
|
1322
|
+
loadSessions(true);
|
|
1323
|
+
})();
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
"""
|
|
1327
|
+
|
|
1328
|
+
_TEMPLATE = """<!DOCTYPE html>
|
|
1329
|
+
<html lang="en">
|
|
1330
|
+
<head>
|
|
1331
|
+
<meta charset="utf-8">
|
|
1332
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1333
|
+
<title>__TITLE__</title>
|
|
1334
|
+
<style>__STYLE__</style>
|
|
1335
|
+
</head>
|
|
1336
|
+
<body>
|
|
1337
|
+
__BODY__
|
|
1338
|
+
<script>__SCRIPT__</script>
|
|
1339
|
+
</body>
|
|
1340
|
+
</html>
|
|
1341
|
+
"""
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def _escape_html(text: str) -> str:
|
|
1345
|
+
"""Escape ``text`` for safe interpolation into HTML text and attributes."""
|
|
1346
|
+
return (
|
|
1347
|
+
text.replace("&", "&")
|
|
1348
|
+
.replace("<", "<")
|
|
1349
|
+
.replace(">", ">")
|
|
1350
|
+
.replace('"', """)
|
|
1351
|
+
.replace("'", "'")
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def render_dashboard_html(title: str = "CODEVANTA") -> str:
|
|
1356
|
+
"""Render the single-page dashboard as one self-contained HTML string.
|
|
1357
|
+
|
|
1358
|
+
The document inlines its CSS and JavaScript, references no external
|
|
1359
|
+
resource (no CDN, no font, no image, no absolute URL) and talks only to the
|
|
1360
|
+
same-origin ``/api/*`` routes of :mod:`codevanta.gui.server`. Results are
|
|
1361
|
+
written with ``textContent``, so analysed data is never interpreted as
|
|
1362
|
+
markup.
|
|
1363
|
+
|
|
1364
|
+
Args:
|
|
1365
|
+
title: Page title, used for both ``<title>`` and the heading. HTML
|
|
1366
|
+
special characters are escaped; a blank title falls back to
|
|
1367
|
+
``"CODEVANTA"``.
|
|
1368
|
+
|
|
1369
|
+
Returns:
|
|
1370
|
+
The complete HTML document.
|
|
1371
|
+
|
|
1372
|
+
Raises:
|
|
1373
|
+
TypeError: If ``title`` is not a string.
|
|
1374
|
+
"""
|
|
1375
|
+
if not isinstance(title, str):
|
|
1376
|
+
raise TypeError(f"title must be a string, got {type(title).__name__}")
|
|
1377
|
+
page = (
|
|
1378
|
+
_TEMPLATE.replace("__STYLE__", _STYLE)
|
|
1379
|
+
.replace("__BODY__", _BODY)
|
|
1380
|
+
.replace("__SCRIPT__", _SCRIPT)
|
|
1381
|
+
)
|
|
1382
|
+
# Applied last so a title that happens to look like a placeholder cannot be
|
|
1383
|
+
# expanded by a later substitution.
|
|
1384
|
+
return page.replace("__TITLE__", _escape_html(title.strip() or "CODEVANTA"))
|