Repository navigation
Releases
v4.5.3
SpyTV v4.5.1 — TV Security Assessment Platform
Release v4.5.1
Phase 1 — Error-correct & refactor
- Fixed Kotlin MainActivity: removed uninitialized lateinit vars (webView/errorView/buttons) and all dead references
- Replaced WebView loading with system browser intent for connect-remote mode
- Removed unused WebView/WebViewClient imports
Phase 2 — Repo cleanup
- Removed pycache directories and android/app/build from disk
- Added .opencode/ to .gitignore
- Confirmed no secrets tracked (.env, *.key, *.pem all ignored-only)
Phase 3 — file_structure.txt
- Regenerated from git ls-files (186 tracked files, indented tree with root folder comments)
Phase 4 — Vulnerability sweep
- pip-audit: 0 Python vulnerabilities
- npm audit fix: resolved 3 Node vulnerabilities (electron, fast-uri, undici) → 0 remaining
- GitHub Dependabot: all alerts in 'fixed' state (0 open)
- No hardcoded credentials or dead URLs found
Phase 5 — Test all code
- 53 pytest tests pass
- compileall: all Python files compile
- node --check: tvreaper.js and c2_dashboard.html script block OK
- All YAML configs parse correctly
Phase 6 — Debug
- All threading.Lock usages use context managers (no deadlocks)
- No stale Gradle processes; port 8443 clear
Phase 7 — Release assets
- Built SpyTV.apk (6,435,918 bytes) via Gradle assembleDebug
- Verified: zip integrity, apksigner signature, aapt badging (package com.authenticated.spytv, launcher MainActivity)
- Generated SHA256SUMS.txt for SpyTV.apk and SpyTV-C2-standalone.exe
Phase 8-9 — Push & merge
- All changes committed and pushed to main
- main is the default branch
Assets
- SpyTV.apk — signed Android APK (debug)
- SpyTV-C2-standalone.exe — standalone C2 executable
- SHA256SUMS.txt — checksums for all assets
SpyTV v1.2.1 - TV header icon, free AI models, electron build config
SpyTV v1.2.1
Summary
Bug-fix and feature release over v1.2.0: TV header icon, 13 new free AI provider models, Electron build config improvements, missing python-nmap dependency fix. Ships verified APK + standalone EXE.
Changes
| File | Change |
|---|---|
c2_dashboard.html |
Added TV header icon (SVG) with .header-tv-icon styles |
c2_cve_feed.py |
Added 13 free OpenRouter models (Nemotron 3 family, Laguna, Inkling, Dots3, Ling) |
android/.../C2Server.kt |
Added matching model entries for the Android standalone C2 |
electron/package.json |
Version bump 1.0.7 -> 1.1.0; extraResources bundle; darwin dmg target; asar false |
electron/main.js |
Removed unused DASHBOARD_URL; hardened frameless window startMove call |
electron/preload.js |
App version 1.0.2 -> 1.1.0 |
requirements.txt |
Added missing python-nmap>=0.6.1 (required by core/scanner.py; was breaking pytest collection) |
SHA256SUMS.txt |
Regenerated for rebuilt assets |
Release Artifacts
| Asset | Size |
|---|---|
SpyTV.apk |
~6.1 MB |
SpyTV-C2-standalone.exe |
~25 MB |
SHA256 checksums are in the SHA256SUMS.txt file included in the repository.
Security
- pip-audit: No known vulnerabilities in Python dependencies.
- npm audit: 8 moderate vulnerabilities from
sprintf-js(used transitively byelectron-builder). All versions vulnerable, no patched release published. Fix via--forcerequires downgradingelectron-builder(breaking change, forbidden by policy). Accepted risk, dev-only build tooling. - GitHub Dependabot: 1 open moderate alert (#55,
sprintf-jstransitive). Same accepted rationale. - Hardcoded credentials: None found in tracked files.
Pipeline Summary (Phases 1-10)
| Phase | Description | Status |
|---|---|---|
| 1 | Error-correct (nmap dep, JS duplicates verified clean) | Completed |
| 2 | Repo cleanup (temp scripts, __pycache__, electron/node_modules, stale port-8443 process killed) |
Completed |
| 3 | file_structure.txt regenerated |
Completed (prior run) |
| 4 | Vulnerability sweep (pip-audit clean, npm 8 accepted, dependabot 1 accepted) | Completed |
| 5 | pytest tests/ — 116 passed; compileall clean; tvreaper.js + electron JS syntax OK; YAML valid |
Completed |
| 6 | Debug (port 8443 freed, no Gradle races, PowerShell ; separators) |
Completed |
| 7 | APK rebuilt + verified (apksigner, aapt), EXE rebuilt, SHA256SUMS regenerated |
Completed |
| 8 | Committed (deaf176) |
Completed |
| 9 | Pushed to main (only branch, already default) |
Completed |
| 10 | Tagged v1.2.1 |
Completed |
SpyTV v1.2.0 — Payload Generator Target IP Dropdown + Custom IP
Payload Generator UX Improvements
Payload Generator Tab
- Target IP dropdown — the Target IP field is now a
<select>dropdown (was a free-text input)
that auto-populates from scan results and connected devices markedvulnerable: true - Exploit button moved left — Generate and Exploit now sit on the left of the form row
- Custom IP button — new button on the far right prompts for an IPv4 address, validates it,
and adds it to the Target IP dropdown
Build Artifacts
| File | Size |
|---|---|
| SpyTV.apk | 6,430,665 bytes |
| SpyTV-C2-standalone.exe | 37,672,041 bytes |
| SHA256SUMS.txt | Checksum manifest |
Both artifacts rebuilt with the latest dashboard HTML. The APK runs as a full standalone C2 server
on Android (default mode) or as a console connecting to a remote C2. The EXE is a PyInstaller
one-shot bundle that runs the C2 server without Python installed.
Verification
- 116 pytest tests pass (all green)
- Dashboard UI tests pass (Node harness, 67 assertions)
- APK verified: package
com.authenticated.spytv, launcherMainActivity, apksigner verify passed
SpyTV v1.1.1 - Dashboard deduplication, f-string fixes
SpyTV v1.1.1
Summary
Bug-fix release over v1.1.0: removes duplicate dashboard code, fixes f-string inconsistencies, and ships verified release artifacts.
Changes
| File | Change |
|---|---|
c2_dashboard.html |
Removed duplicate tab-exploits div, duplicate nav item, duplicate JS function definitions (second set of loadProviderModels, generateExploitForCVE, generatePatchForCVE, loadExploits, updateCVEsFromFeed), duplicate switchTab override |
c2_dashboard.html |
Fixed typo: "Regate Exploit" -> "Regenerate Exploit" |
c2_cve_feed.py |
Fixed unnecessary f-string (f"CVE not found in database" -> "CVE not found in database") |
c2_cve_feed.py |
Fixed redundant cve.get("tv_type", cve.get("tv_type", "smart-tv")) -> cve.get("tv_type", "smart-tv") |
c2_server.py |
get_gateway_and_subnet() consistently returns dotted subnet mask |
android/app/src/main/assets/c2_dashboard.html |
Synced with root dashboard |
Release Artifacts
| Asset | Size |
|---|---|
SpyTV.apk |
~6.1 MB |
SpyTV-C2-standalone.exe |
~17.2 MB |
SHA256 checksums are in the SHA256SUMS.txt file included in the repository.
Security
- npm audit: 8 moderate vulnerabilities from
sprintf-js(used transitively byelectron-builder). All versions ofsprintf-jsare vulnerable with no patched release. The only fix vianpm audit fix --forcerequires downgradingelectron-builder, which is a breaking change and is explicitly forbidden by the project's security policy. These are documented as accepted risks in this release. - pip-audit: No known vulnerabilities in Python dependencies (colorama, pyyaml, requests, urllib3, reportlab).
- GitHub Dependabot: All alerts on default branch resolved (10 alerts, all fixed).
- Hardcoded credentials: None found in tracked files.
Pipeline Summary (Phases 1-10)
| Phase | Description | Status |
|---|---|---|
| 1 | Error-correction & refactor | Completed |
| 2 | Repo cleanup (remove junk dirs, extend .gitignore) | Completed |
| 3 | Regenerate file_structure.txt |
Completed |
| 4 | Vulnerability sweep (pip-audit, npm audit, dependabot) | Completed |
| 5 | Test suite (pytest tests/) |
All passed |
| 6 | Debug (port 8443 free, no Gradle races) | Verified |
| 7 | Build release assets (APK, EXE, SHA256SUMS) | Completed |
| 8 | Commit changes | Committed (2ead0a6) |
| 9 | Push to remote | Pushed to main |
| 10 | Tag & release | Tagged v1.1.1 |
v1.1.0 — TV CVE additions from Quantum CLI
Release highlights:
TV CVE Catalog Expansion (11 new CVEs from Quantum CLI)
- CVE-2026-0073 (ADB TLS auth bypass, CVSS 8.8 HIGH)
- CVE-2017-13077 (KRACK WPA2, CVSS 6.8 MEDIUM)
- CVE-2023-45866 (BT HID injection, CVSS 6.3 MEDIUM)
- CVE-2017-0781 (BlueBorne BNEP RCE, CVSS 8.8 HIGH)
- CVE-2017-0785 (BlueBorne SDP info disclosure, CVSS 6.5 MEDIUM)
- CVE-2018-5383 (KNOB Bluetooth pairing, CVSS 6.8 MEDIUM)
- CVE-2020-0022 (BlueFrag L2CAP, CVSS 8.8 HIGH)
- CVE-2017-7494 (SambaCry RCE, CVSS 9.8 CRITICAL)
- CVE-2022-0847 (Dirty Pipe COW, CVSS 7.8 HIGH)
- CVE-2016-5195 (Dirty COW, CVSS 7.0 HIGH)
- CVE-2024-6387 (regreSSHion sshd race, CVSS 8.1 HIGH)
Dashboard & Exploit Catalog
- Exploit dashboard now shows all 92 built-in exploits per CVE with per-row View Whitepaper button
- Composite row IDs eliminate duplicate CVE-ID collisions across families
- runExploitRow appends res.detail to success log
- 39 tests passing (test_autonomous.py + test_audit_flow.py)
Android Parity
- C2Server.kt TV_DATABASE expanded 1:1 with 11 new CVEs per family
- Kotlin compile EXIT=0
- Android asset c2_dashboard.html synced (contract test passes)
Screenshots
- docs/screenshots/: dashboard-web.png, exe-running.png, apk-running.png
Whitepapers
- 11 new whitepapers in docs/whitepapers/ (NVD CVSS 3.1 primary scores)
Excluded
- CVE-2022-44319 / CVE-2023-24033 (smartphone Exynos baseband, not TV-relevant per NVD)
Release Assets
- SpyTV-C2-standalone.exe (37.6 MB)
- SpyTV.apk (5.3 MB release APK)
- SHA256SUMS.txt
SpyTV v1.1.0 — TV Security Assessment Platform
SpyTV v1.1.0 — Dashboard Enhancements, Network Info, Exploits Tab
New Features
- New CVE Feed panel with Update button (refreshes from NVD in-place)
- Network info in C2 Server Info tab: Gateway IPv4 + Subnet Mask
- Provider/Model dropdown — auto-populated from /api/providers with all configured AI providers
- Generate Exploit / Add Patch buttons on each CVE feed item with status badges
- Exploits dashboard tab — lists all generated exploit modules with success rates
- Fixed test generation HTTP 400 error — model validation now prevents mismatched provider/model combos
Android APK
- APK can run as standalone full C2 server (same capabilities as Web/EXE)
- Default to Self-Hosted C2 mode with mode selection dialog on app open
- Fixed intToInetAddress/unresolved reference — manual intToIpString() conversion
- Fixed DhcpInfo.ipMask -> netmask field name
Security
- urllib3>=2.8.0, requests>=2.32.0 (pip-audit clean)
- .pytest_cache/ and .mypy_cache/ added to .gitignore
Assets
- SpyTV.apk — Android APK (standalone C2 server + Console mode)
- SpyTV-C2-standalone.exe — Windows standalone EXE (full C2 server)
- SHA256SUMS.txt — checksums for both assets
SpyTV v1.0.9 - Default Self-Hosted C2 + Mode Selection
SpyTV v1.0.9 - Default Self-Hosted C2 + Mode Selection
All platforms now default to Self-Hosted C2 mode with a clear mode selection dialog when the app opens:
- Self-Hosted: This device runs the full C2 server. Dashboard at http://localhost:8443/ (or http://:8443/ on Android). This is the default.
- Connect-Remote: Connect this device to an existing C2 server on another machine by entering its URL.
What changed
- APK: First-run dialog shows Setup C2 Server with Self-Hosted | Connect-Remote buttons. Self-Hosted is now the default mode. Mode toggle in settings (top-right switch).
- EXE: run_c2.bat shows mode selection menu. c2_server.py adds interactive prompt.
- Web: python c2_server.py shows mode selection prompt.
Security fixes
- Fixed moderate vulnerability: urllib3 pinned to >=2.8.0 (was 2.7.0)
- requests pinned to >=2.32.0
- Added .github/dependabot.yml for automated dependency updates
- Replaced os.system shell calls with shutil.which() in core/exploit_dispatcher.py, families/webos/install.py, families/tizen/install.py
Release assets
| Asset | Description |
|---|---|
| SpyTV.apk | Android APK (Self-Hosted + Connect-Remote modes) |
| SpyTV-C2-standalone.exe | Windows EXE (bundled Python, Self-Hosted C2) |
| SHA256SUMS.txt | Checksum manifest |
SpyTV v1.0.7 - honest verdicts and a working Exploit button
SpyTV v1.0.7
Three reports, and all three turned out to be the same shape of bug: a value
that was decided before it was proven. A host was called vulnerable
because a port was open; a device was sent to DLNA because a lookup missed;
a television was identified by whichever ports happened to survive a burst of
64 simultaneous connections.
Fixed
1. A device showed as vulnerable with no device type and no CVEs
c2_ops.scan_network() set vulnerable: len(open_ports) > 0. The flag meant
"this host exists". perform_scan() then only ever raised it — after a
CVE list matched, after a family signature matched — and never lowered it, so
a host that matched nothing kept discovery's guess:
10.1.10.35 ports=80 device_type="Unknown" vulnerable=true (no cves)
It also contradicted the control next to it. /api/check had always computed
vulnerable: bool(finding) and bool(cves), so Check answered "not
confirmed" for a device Scan had just called vulnerable — which is why the
report sounded impossible.
Fix. Discovery no longer decides. scan_network() records
vulnerable: False, detected: True, and one function — finalize_findings() —
sets the verdict: vulnerable only if a CVE list matched or a family
signature was recognised, otherwise merely detected. severity is now
computed for both sources (a device found only by its signature port got
cve_ids but no severity), and device_type takes the matched family's name.
The table agrees: an unmatched host now renders as Unidentified device
with a neutral Not confirmed badge instead of an amber Vulnerable sitting
over an empty CVE column that read as Safe.
Measured on the real pipeline, then again over POST /api/scan:
| host | open ports | device_type | vulnerable | CVEs |
|---|---|---|---|---|
10.2.0.1 (TV) |
16 | Samsung Tizen TV | true |
5 |
10.2.0.2 (this machine, :80) |
1 | Unidentified | false |
0 |
Findings claiming vulnerable with zero CVEs: 0 (was 1).
2. The row "Exploit" button never ran anything — and always chose DLNA
exploitTarget() looked the device up in a map keyed 'Samsung Tizen',
'LG webOS', 'Roku'… while the row passes r.name || r.device_type, i.e.
'Samsung Tizen TV', 'LG webOS TV', 'Roku TV'. No key ever matched.
The || 'dlna' fallback won for every device (only chromecast was rescued by
a special case). Driving the real script in Node:
exploitTarget('10.1.10.41', 'LG webOS TV') -> payload-family: "dlna"
DLNA has no entry in TV_CVE_DATABASE or CVE_EXPLOITS, so /api/exploit
could only answer 404 no exploit module. And even that was never shown: the
function only pre-filled a form and switched tabs, writing nothing to the
card. A click produced no visible effect at all — "the exploit button doesn't
work".
Fix. The family now resolves from the row's own tv_type through a new
TVTYPE_TO_FAMILY table (keys taken from TV_CVE_DATABASE), and the button
does what its label says:
- recognised family and confirmed CVEs → runs the module and reports the
real outcome on the card; - recognised family, no confirmed CVE → loads the target, does not run, and
says why (Check to verify, or Exploit to run anyway); - no identifiable family → refuses and leaves the selector alone rather
than silently claiming DLNA.
Two more gaps found while auditing this: select.value only sticks when the
option exists, so Hisense and TCL were unreachable — both are in the CVE
database, the exploit registry and both family maps, but had no <option>.
Added. And deployPayload() posted tv_type: null, which arrived server-side
as the literal string 'None' (truthy) and skipped family lookup; both ends
now normalise null to "".
3. Scan and Check disagreed about which television a host was
Seconds apart on the same host: /api/scan said LG webOS (4 CVEs),
/api/check said Samsung Tizen (5 CVEs). Two different CVE sets and two
different exploit modules for one device.
detect_tv_type() is first-match over database order, so the winner depends
entirely on which ports the caller saw — and the scan gets its ports from the
pooled probe while Check probes sequentially. Measured against a device that
accepts every connection (16 signature ports, sequential reference 16/16):
workers=64 -> 3/16 workers=8 -> 13/16
workers=16 -> 8/16 workers=4 -> 16/16 (2 and 1 also 16/16)
64 concurrent SYNs to one device drop most of them, and the ports that
survive decide the family. Here all four Tizen ports were lost and only 3000
survived — hence "LG webOS".
Fix. probe_many() now caps in-flight probes per address
(PER_HOST_WORKERS = 4) and builds its queue port-major, so parallelism
spreads across hosts instead of piling onto the first few addresses. Results
are still returned in job order, which every caller maps back by index.
Re-measured: 16/16, scan_network() reports samsung-tizen, in 1.4s —
no slowdown, and each device now sees a fraction of the previous fan-out.
4. Controls audited: family / CVE / TV type / connection type
| Control | Samsung | LG | Android/Fire | Roku | Vizio | Chromecast | Hisense | TCL | DLNA |
|---|---|---|---|---|---|---|---|---|---|
| CVE database | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
| Exploit module | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ none |
| Family map (server + dashboard) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | null |
| Payload selector | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | added | added | ✅ payload only |
| Row Check / Exploit / Patch | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | refused, honestly |
| Devices-tab CVE chips + Patch | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | n/a |
- DLNA has no exploit module.
/api/exploitrefuses it with
404 {error, attempted:false, known_families:[…]}— verified live. Payload
generation for DLNA works (generic connect-back), so it stays selectable;
the refusal now happens where you can see it. generate_payload()'s fallback was labelled "Generic DLNA Exploit Payload"
and registeredfamily:"Unknown"for every family without a dedicated
install vector — which is what left connected Hisense/TCL/Chromecast devices
with no device type and no CVE profile. It now names and registers the
family actually requested.- Patch only wrote to the activity log, and it could not use the scan
status helper (that targets the results table itself). It now reports into a
dedicated slot under the table.
Also
- A flaky test that made the suite untrustworthy.
test_generate_exploit_without_keyfailed about half of full-suite runs with
[WinError 10053] An established connection was abortedand passed in
isolation. The stub server answeredPOSTwithout ever reading the request
body, then closed — leaving the client mid-write. Windows reports that on
the client. It now drains the body before responding.
Verification
| Check | Result |
|---|---|
| Full pytest suite | 114 passed, 3 consecutive runs |
| New probe-pool tests | fail unless the per-host cap, the cross-host spread and job ordering hold |
| New verdict tests | with the old logic restored: {'ip': '10.1.10.35', 'open_ports': [80], 'device_type': 'Unknown', 'vulnerable': True} — the exact report |
| Dashboard script parse | node --check clean |
| Dashboard behaviour (Node, stub DOM) | 90 assertions, 0 failures (14 sections) |
Live POST /api/scan |
2 findings, 0 claiming vulnerable with no CVE |
Live POST /api/check |
agrees with Scan on the same hosts |
Live POST /api/exploit (DLNA) |
404 attempted:false + known_families — nothing ran |
| Devices tab, all implant families | 9/9 cards, CVE chips + Patch present; Samsung(Tizen), LG(webOS), AndroidTV, RokuTV, Vizio, chromecast-cast, hisense, tcl, Fire TV all resolve; Unknown/unknown correctly resolve to no profile |
| Pooled vs sequential probe accuracy | 3/16 → 16/16 |
The dashboard suite executes the whole inline script in Node against a stub DOM
and skips when node is absent (node is already required for the Electron
build). It also covers the inline handlers themselves: an apostrophe in a
device name is escaped with the new jsAttr(), because escapeHtml() leaves
apostrophes alone (correct for HTML text) and one inside a single-quoted
onclick argument would make the handler a SyntaxError — a button that does
nothing, which is precisely what this release is about.
Upgrading
A running C2 executes the code it was started with — restart it:
python c2_server.py
No Android source changed. The desktop shell (electron/*.js, its index.html
and offline.html) did not change either; only its version string was bumped,
so the shipped exe is rebuilt solely to match this tag.
Artifacts
| File | Size | SHA-256 |
|---|---|---|
SpyTV.apk |
6,294,138 B | 6395ff97891500c8b53067b28a3a4f4574af2dce4f405c13f24c3daba1deca1f |
SpyTV-C2-1.0.7-portable.exe |
100,109,261 B | 061eb2d3fbb5307336982f004e320b537f2667b2417afaecacd3fd81ce826e5b |
SHA256SUMS.txt |
172 B | matches both entries above |
SpyTV.apk is byte-identical to v1.0.5 and v1.0.6 — no Android source changed,
so it is re-attached unchanged rather than rebuilt. The exe is rebuilt only
because electron/package.json carries the version; its build.files is
main.js, preload.js, index.html, offline.html and none of those
changed.
SpyTV v1.0.6 - C2 startup crash, silent dashboard buttons
SpyTV v1.0.6
Three of the four things that made the console feel broken came down to code
that failed without saying so. This release fixes the C2's own startup
crash and makes every button report what happened.
Fixed
1. The C2 could exit before it served a single request (critical)
main() prints a startup hint containing ⚙. On Windows, a piped or
redirected stdout is encoded with the ANSI code page (cp1252), which has no
such character — print() raised UnicodeEncodeError.
Because the order in main() is bind the port → print the banner → print the
hint → serve_forever(), the exception fired after the port was bound but
before the server started serving. The process died during startup: no
dashboard, no API, and the only visible effect was a terminal window that
opened with a traceback — which is what "the C2 keeps restarting, opening a
terminal window again" looks like from the operator's side.
An interactive console doesn't hit this (Python writes through the Unicode
console API), so it only surfaced whenever output was captured or redirected.
Fix: _harden_console_output() runs first in main() and reconfigures
sys.stdout / sys.stderr with errors="replace". Each stream keeps its own
encoding — interactive consoles still render ⚙ correctly — and substitutes a
placeholder for anything it cannot represent. No print() can be fatal again.
2. The Scan button failed without a message
startScan() had no error handling. Any failure — non-200, non-JSON body,
missing results, network error — died as an unhandled rejection, so the
results panel stayed exactly as it was and the log stopped at
"Starting network scan...". A dead button.
It now disables and relabels the button (Scanning...), writes a progress
note into the results panel itself, validates that results is a list, and
on failure shows a warning panel naming the reason while keeping the previous
rows. The button is restored in finally, so every path re-enables it.
Evidence from your own C2's history: 8 scans between 13:06 and 13:13, one
every 40–80s, each returning the same single host — with no spinner, no
disabled button and no in-panel feedback, a minute of silence looks like
nothing happened.
3. Client-details submit could hang with the button stuck
api() was await fetch(...); return r.json() — no timeout, no status
check, no non-JSON handling. fetch has no default timeout, so a C2 that
never answered left the promise pending forever: submitClientForm()'s
finally never ran, the Continue button stayed disabled and the modal never
closed.
api() now arms an AbortController per call — 30s by default, 600s
for /api/cve/patch (it stacks a 45s-per-command cap across several commands)
and 300s for scan / exploit / patch / cve — tolerates empty and non-JSON
replies, and rethrows timeouts and network failures as messages you can act on
("check that the C2 is running"). The
submit button also shows Submitting... while in flight.
4. Error bodies no longer blank the CVE tab or the device grid
renderCVEs() tested !cves || cves.length === 0. A failed /api/cves reply
is an object, so length was undefined, the empty-check passed, and
cves.map threw — the tab went blank with no message. The loaders
(loadDevices / loadClips / loadLocalIp) now only adopt a well-formed
reply, so a 500 or timeout can no longer overwrite the last good state.
5. The Exploit Payload Generator's Generate button did nothing
generatePayload() was the only generate path with no error handling: no
try/catch, no res.error check, no button state. It validated the form with
alert() — a suppressed, backgrounded or missed dialog leaves no trace, so an
empty Target IP looked exactly like a broken button. It also never wrote to the
card's own output container, so every failure was invisible.
The server was healthy the whole time — POST /api/payload measured on your
live C2 returned 200 in 0.07s for tizen-dev, webos-dev and dlna, the
route exists, and every element id the function touches is present — which is
what pointed at the client.
Now: inline validation on the card (never alert()), a Generating...
disabled button, a surfaced res.error, a reply without a payload reported
instead of opening a preview full of undefined, and the preview title reset
(it is shared with the AI generator, which retitles it to the CVE id).
deployPayload() got the same treatment.
Verification
| Check | Result |
|---|---|
| Full pytest suite | 107 passed (+1 startup crash, +2 dashboard) |
| Startup crash test | fails with the fix reverted: UnicodeEncodeError … '\u2699' |
| Dashboard script parse | node --check clean (before and after) |
| Dashboard behaviour (Node, stub DOM) | 67 assertions, 0 failures (13 sections) |
| Live scan against a new-code C2 | 200 in 38.1s, found=2, results is a list |
| Live authorize (client details) | 200 in 1.02s, PDF preview 0.02s |
| Live authorize while a scan runs | 200 in 0.43s — did not block |
The dashboard suite (tests/test_dashboard_ui.py) executes the whole inline
script in Node against a stub DOM: happy path, server rejection, unreachable
C2, malformed reply, the renderScanResults(undefined) guard, both
client-details outcomes, the bounded hang, the per-endpoint budgets and the new
list guards. It skips when node is absent (node is already required for the
Electron build).
Upgrading
A running C2 executes the code it was started with — restart it to pick up
this release:
python c2_server.py
No Android or desktop-shell source changed, so the app you already have
loads the fixed dashboard from the restarted C2.