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.
Files changed (65) hide show
  1. codevanta/__init__.py +1 -0
  2. codevanta/analysis/__init__.py +1 -0
  3. codevanta/analysis/binary_diff.py +1168 -0
  4. codevanta/analysis/callgraph.py +542 -0
  5. codevanta/analysis/cfg.py +1201 -0
  6. codevanta/analysis/dataflow.py +762 -0
  7. codevanta/analysis/debugger.py +289 -0
  8. codevanta/analysis/debuginfo.py +304 -0
  9. codevanta/analysis/decompile.py +1094 -0
  10. codevanta/analysis/decompilers/__init__.py +250 -0
  11. codevanta/analysis/decompilers/angr_backend.py +251 -0
  12. codevanta/analysis/decompilers/base.py +278 -0
  13. codevanta/analysis/decompilers/ghidra_backend.py +316 -0
  14. codevanta/analysis/dynamic.py +756 -0
  15. codevanta/analysis/emulate.py +1349 -0
  16. codevanta/analysis/evidence_bundle.py +736 -0
  17. codevanta/analysis/function_hash.py +562 -0
  18. codevanta/analysis/graph_export.py +441 -0
  19. codevanta/analysis/html_view.py +695 -0
  20. codevanta/analysis/jump_tables.py +546 -0
  21. codevanta/analysis/solver.py +715 -0
  22. codevanta/analysis/symbolic.py +1809 -0
  23. codevanta/analysis/trace.py +646 -0
  24. codevanta/analysis/type_propagation.py +1055 -0
  25. codevanta/analysis/type_recovery.py +1214 -0
  26. codevanta/analysis/vtables.py +572 -0
  27. codevanta/analysis/xrefs.py +434 -0
  28. codevanta/api/__init__.py +1 -0
  29. codevanta/cli/__init__.py +1 -0
  30. codevanta/cli/main.py +3038 -0
  31. codevanta/core/__init__.py +31 -0
  32. codevanta/core/disassembler.py +121 -0
  33. codevanta/core/entropy.py +237 -0
  34. codevanta/core/functions.py +176 -0
  35. codevanta/core/loader.py +1351 -0
  36. codevanta/core/report.py +306 -0
  37. codevanta/core/strings.py +109 -0
  38. codevanta/evidence/__init__.py +7 -0
  39. codevanta/evidence/confidence.py +36 -0
  40. codevanta/evidence/models.py +43 -0
  41. codevanta/evidence/provenance.py +37 -0
  42. codevanta/gui/__init__.py +46 -0
  43. codevanta/gui/layout.py +1384 -0
  44. codevanta/gui/server.py +1305 -0
  45. codevanta/ir/__init__.py +6 -0
  46. codevanta/ir/adapters/__init__.py +31 -0
  47. codevanta/ir/base.py +31 -0
  48. codevanta/ir/instruction.py +42 -0
  49. codevanta/mcp_server/__init__.py +5 -0
  50. codevanta/mcp_server/server.py +710 -0
  51. codevanta/mcp_server/tools.py +1364 -0
  52. codevanta/plugins/__init__.py +748 -0
  53. codevanta/setup/__init__.py +28 -0
  54. codevanta/setup/apply.py +22 -0
  55. codevanta/setup/clients.py +285 -0
  56. codevanta/setup/plan.py +79 -0
  57. codevanta/setup/verify.py +43 -0
  58. codevanta/storage/__init__.py +1 -0
  59. codevanta/storage/session.py +514 -0
  60. codevanta-0.1.0.dist-info/METADATA +688 -0
  61. codevanta-0.1.0.dist-info/RECORD +65 -0
  62. codevanta-0.1.0.dist-info/WHEEL +5 -0
  63. codevanta-0.1.0.dist-info/entry_points.txt +2 -0
  64. codevanta-0.1.0.dist-info/licenses/LICENSE +21 -0
  65. codevanta-0.1.0.dist-info/top_level.txt +1 -0
@@ -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 &mdash; 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 &middot; 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 &mdash; 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 &rsaquo;</button>
453
+ </div>
454
+ </div>
455
+ <p class="hero-hint">
456
+ The file is only read from disk &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; whole region</option>
542
+ <option value="recursive">recursive &mdash; 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 &mdash; 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 &mdash; 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 &mdash; nothing is executed on your machine &mdash; 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 &mdash; 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 &mdash; 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("&", "&amp;")
1348
+ .replace("<", "&lt;")
1349
+ .replace(">", "&gt;")
1350
+ .replace('"', "&quot;")
1351
+ .replace("'", "&#39;")
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"))