From 1e2047c03316549b6f52c51749fc9cf5f60f75ff Mon Sep 17 00:00:00 2001 From: UNITRONIX <36471318+UNITRONIX@users.noreply.github.com> Date: Tue, 24 Mar 2026 00:26:25 +0100 Subject: [PATCH] BetterDesk 3.0.0 Alpha --- .dockerignore | 2 + .github/SECURITY_AUDIT_2026-03-01.md | 676 ------ .github/copilot-instructions.md | 80 +- .gitignore | 187 +- Dockerfile | 4 +- Dockerfile.console | 4 +- Dockerfile.server | 4 +- README.md | 13 +- VERSION | 2 +- betterdesk-agent/README.md | 179 ++ betterdesk-agent/agent/agent.go | 746 +++++++ betterdesk-agent/agent/clipboard.go | 79 + betterdesk-agent/agent/config.go | 162 ++ betterdesk-agent/agent/filebrowser.go | 157 ++ betterdesk-agent/agent/manifest.go | 181 ++ betterdesk-agent/agent/screenshot_unix.go | 50 + betterdesk-agent/agent/screenshot_windows.go | 38 + betterdesk-agent/agent/system.go | 121 ++ betterdesk-agent/agent/terminal.go | 33 + betterdesk-agent/agent/terminal_unix.go | 68 + betterdesk-agent/agent/terminal_windows.go | 76 + betterdesk-agent/go.mod | 20 + betterdesk-agent/go.sum | 40 + betterdesk-agent/install/install.ps1 | 150 ++ betterdesk-agent/install/install.sh | 174 ++ betterdesk-agent/main.go | 106 + betterdesk-docker.sh | 7 +- betterdesk-server/admin/server.go | 2 + betterdesk-server/api/auth_handlers.go | 11 +- betterdesk-server/api/branding_handlers.go | 607 ++++++ betterdesk-server/api/cdap_handlers.go | 739 +++++++ betterdesk-server/api/client_api_handlers.go | 22 +- betterdesk-server/api/server.go | 88 +- betterdesk-server/cdap/alerts.go | 284 +++ betterdesk-server/cdap/api.go | 25 + betterdesk-server/cdap/audio.go | 240 +++ betterdesk-server/cdap/clipboard.go | 99 + betterdesk-server/cdap/crypto.go | 180 ++ betterdesk-server/cdap/delegation.go | 176 ++ betterdesk-server/cdap/desktop.go | 276 +++ betterdesk-server/cdap/filebrowser.go | 271 +++ betterdesk-server/cdap/gateway.go | 191 +- betterdesk-server/cdap/handler.go | 182 +- betterdesk-server/cdap/manifest.go | 141 +- betterdesk-server/cdap/media_control.go | 424 ++++ betterdesk-server/cdap/terminal.go | 263 +++ betterdesk-server/cdap/video.go | 205 ++ betterdesk-server/config/config.go | 18 +- betterdesk-server/db/database.go | 2 + betterdesk-server/db/postgres.go | 29 + betterdesk-server/db/sqlite.go | 33 + betterdesk-server/main.go | 8 + betterdesk.ps1 | 194 +- betterdesk.sh | 208 +- bridges/README.md | 108 + bridges/modbus/bridge_modbus.py | 259 +++ bridges/modbus/config.example.json | 78 + bridges/modbus/requirements.txt | 1 + bridges/rest-webhook/bridge_rest.py | 273 +++ bridges/rest-webhook/config.example.json | 63 + bridges/rest-webhook/requirements.txt | 1 + bridges/snmp/bridge_snmp.py | 251 +++ bridges/snmp/config.example.json | 78 + bridges/snmp/requirements.txt | 1 + docker-compose.single.yml | 10 +- docker-compose.yml | 2 +- docker-entrypoint.sh | 26 + docker/entrypoint.sh | 8 + docs/README.md | 74 +- .../BETTERDESK_CLIENT_ARCHITECTURE.md | 0 docs/architecture/BETTERDESK_v3_OVERVIEW.md | 325 +++ .../CDAP_IMPLEMENTATION_PLAN.md | 0 docs/architecture/CDAP_PROTOCOL.md | 921 ++++++++ docs/{ => architecture}/PORT_SECURITY.md | 0 docs/{ => architecture}/PROJECT_STRUCTURE.md | 10 +- docs/{ => development}/CHANGELOG.md | 32 + docs/{ => development}/CONTRIBUTING.md | 0 .../CONTRIBUTING_TRANSLATIONS.md | 0 docs/{ => development}/TRANSLATION_SUMMARY.md | 12 +- docs/{ => docker}/DOCKER_MIGRATION.md | 0 .../docker/DOCKER_QUICKSTART.md | 4 +- docs/{ => docker}/DOCKER_SUPPORT.md | 0 docs/{ => docker}/DOCKER_TROUBLESHOOTING.md | 0 docs/{ => enterprise}/ENTERPRISE_ROADMAP.md | 0 docs/enterprise/IMPLEMENTATION_PLAN.md | 323 +++ docs/{ => features}/CLIENT_GENERATOR.md | 0 .../CLIENT_GENERATOR_QUICKSTART_EN.md | 0 docs/{ => features}/CUSTOM_DEVICE_API.md | 0 docs/{ => features}/STATUS_TRACKING_v3.md | 0 docs/{ => features}/WEB_REMOTE_CLIENT_PLAN.md | 0 .../GPU_FIX_QUICKSTART_EN.md | 0 docs/{ => performance}/GPU_OPTIMIZATION_EN.md | 0 .../OPTIMIZATION_SUMMARY_EN.md | 4 +- docs/{ => setup}/BUILD_GUIDE.md | 2 +- docs/{ => setup}/HTTPS_SETUP.md | 0 docs/{ => setup}/INSTALLATION_V1.4.0.md | 0 docs/{ => setup}/SYNOLOGY_INSTALLATION.md | 0 docs/{ => setup}/UPDATE_GUIDE.md | 0 .../KEY_TROUBLESHOOTING.md | 0 docs/{ => troubleshooting}/QUICK_FIX_EN.md | 0 .../TROUBLESHOOTING_EN.md | 0 main.go | 520 +++++ sdks/README.md | 90 + sdks/nodejs/README.md | 125 ++ sdks/nodejs/package.json | 22 + sdks/nodejs/src/bridge.js | 410 ++++ sdks/nodejs/src/index.js | 24 + sdks/nodejs/src/protocol.js | 84 + sdks/nodejs/src/widgets.js | 118 ++ sdks/python/README.md | 108 + sdks/python/betterdesk_cdap/__init__.py | 30 + sdks/python/betterdesk_cdap/bridge.py | 386 ++++ sdks/python/betterdesk_cdap/protocol.py | 125 ++ sdks/python/betterdesk_cdap/widgets.py | 242 +++ sdks/python/pyproject.toml | 23 + web-nodejs/config/config.js | 8 + web-nodejs/lang/de.json | 285 ++- web-nodejs/lang/en.json | 420 +++- web-nodejs/lang/es.json | 263 ++- web-nodejs/lang/fr.json | 263 ++- web-nodejs/lang/it.json | 276 ++- web-nodejs/lang/nl.json | 281 ++- web-nodejs/lang/pl.json | 414 +++- web-nodejs/lang/pt.json | 281 ++- web-nodejs/lang/zh.json | 302 ++- web-nodejs/middleware/rateLimiter.js | 9 + web-nodejs/public/css/activity.css | 119 ++ web-nodejs/public/css/automation.css | 181 ++ web-nodejs/public/css/cdap.css | 1414 ++++++++++++- web-nodejs/public/css/dataguard.css | 83 + web-nodejs/public/css/desktop-mode.css | 35 +- web-nodejs/public/css/desktop-widgets.css | 1854 +++++++++++++++++ web-nodejs/public/css/devices.css | 6 +- web-nodejs/public/css/help-requests.css | 272 +++ web-nodejs/public/css/inventory.css | 303 +++ web-nodejs/public/css/main.css | 402 +++- web-nodejs/public/css/network.css | 114 + web-nodejs/public/css/reports.css | 199 ++ web-nodejs/public/css/tenants.css | 269 +++ web-nodejs/public/css/tickets.css | 246 +++ web-nodejs/public/css/tokens.css | 466 +++++ web-nodejs/public/css/tutorial.css | 251 +++ web-nodejs/public/js/activity.js | 191 ++ web-nodejs/public/js/app.js | 11 +- web-nodejs/public/js/automation.js | 368 ++++ web-nodejs/public/js/cdap-audio.js | 378 ++++ web-nodejs/public/js/cdap-desktop.js | 607 ++++++ web-nodejs/public/js/cdap-devices.js | 346 +++ web-nodejs/public/js/cdap-filebrowser.js | 400 ++++ web-nodejs/public/js/cdap-terminal.js | 249 +++ web-nodejs/public/js/cdap-video.js | 285 +++ web-nodejs/public/js/cdap-widgets.js | 708 ++++++- web-nodejs/public/js/dataguard.js | 267 +++ web-nodejs/public/js/desktop-mode.js | 304 ++- web-nodejs/public/js/desktop-widgets.js | 1257 +++++++++++ web-nodejs/public/js/deviceDetail.js | 43 +- web-nodejs/public/js/devices.js | 77 +- web-nodejs/public/js/help-requests.js | 275 +++ web-nodejs/public/js/inventory.js | 296 +++ web-nodejs/public/js/network.js | 305 +++ web-nodejs/public/js/rdclient/client.js | 143 +- web-nodejs/public/js/rdclient/input.js | 81 +- web-nodejs/public/js/rdclient/protocol.js | 7 +- web-nodejs/public/js/registrations.js | 22 +- web-nodejs/public/js/remote.js | 1 + web-nodejs/public/js/reports.js | 290 +++ web-nodejs/public/js/settings.js | 11 +- web-nodejs/public/js/tenants.js | 282 +++ web-nodejs/public/js/tickets.js | 406 ++++ web-nodejs/public/js/tokens.js | 370 ++++ web-nodejs/public/js/tutorial.js | 422 ++++ web-nodejs/public/js/widget-plugins.js | 968 +++++++++ web-nodejs/routes/bd-api.routes.js | 275 +++ web-nodejs/routes/cdap.routes.js | 139 +- web-nodejs/routes/dashboard.routes.js | 28 + web-nodejs/routes/dataguard.routes.js | 8 +- web-nodejs/routes/desktop.routes.js | 118 ++ web-nodejs/routes/index.js | 10 +- web-nodejs/routes/inventory.routes.js | 70 +- web-nodejs/routes/network.routes.js | 38 +- web-nodejs/routes/pages.routes.js | 96 + web-nodejs/routes/registration.routes.js | 72 + web-nodejs/routes/rustdesk-api.routes.js | 14 +- web-nodejs/routes/tokens.routes.js | 104 + web-nodejs/server.js | 61 +- web-nodejs/services/betterdeskApi.js | 234 ++- web-nodejs/services/cdapMediaProxy.js | 165 ++ web-nodejs/services/cdapTerminalProxy.js | 128 ++ web-nodejs/services/dbAdapter.js | 43 +- web-nodejs/services/emailService.js | 3 +- web-nodejs/services/networkMonitor.js | 3 +- web-nodejs/services/wsRelay.js | 11 +- web-nodejs/views/activity.ejs | 69 + web-nodejs/views/automation.ejs | 292 +++ web-nodejs/views/cdap-device.ejs | 37 +- web-nodejs/views/cdap-devices.ejs | 93 + web-nodejs/views/dataguard.ejs | 163 ++ web-nodejs/views/errors/403.ejs | 4 +- web-nodejs/views/errors/404.ejs | 4 +- web-nodejs/views/errors/500.ejs | 4 +- web-nodejs/views/help-requests.ejs | 56 + web-nodejs/views/inventory.ejs | 100 + web-nodejs/views/layouts/base.ejs | 16 +- web-nodejs/views/layouts/main.ejs | 33 +- web-nodejs/views/layouts/viewer.ejs | 34 +- web-nodejs/views/login.ejs | 10 +- web-nodejs/views/network.ejs | 184 ++ web-nodejs/views/partials/navbar.ejs | 9 +- web-nodejs/views/partials/sidebar.ejs | 85 + web-nodejs/views/remote.ejs | 12 +- web-nodejs/views/reports.ejs | 115 + web-nodejs/views/tenants.ejs | 108 + web-nodejs/views/tickets.ejs | 158 ++ web-nodejs/views/tokens.ejs | 237 +++ 214 files changed, 34010 insertions(+), 1269 deletions(-) delete mode 100644 .github/SECURITY_AUDIT_2026-03-01.md create mode 100644 betterdesk-agent/README.md create mode 100644 betterdesk-agent/agent/agent.go create mode 100644 betterdesk-agent/agent/clipboard.go create mode 100644 betterdesk-agent/agent/config.go create mode 100644 betterdesk-agent/agent/filebrowser.go create mode 100644 betterdesk-agent/agent/manifest.go create mode 100644 betterdesk-agent/agent/screenshot_unix.go create mode 100644 betterdesk-agent/agent/screenshot_windows.go create mode 100644 betterdesk-agent/agent/system.go create mode 100644 betterdesk-agent/agent/terminal.go create mode 100644 betterdesk-agent/agent/terminal_unix.go create mode 100644 betterdesk-agent/agent/terminal_windows.go create mode 100644 betterdesk-agent/go.mod create mode 100644 betterdesk-agent/go.sum create mode 100644 betterdesk-agent/install/install.ps1 create mode 100644 betterdesk-agent/install/install.sh create mode 100644 betterdesk-agent/main.go create mode 100644 betterdesk-server/api/branding_handlers.go create mode 100644 betterdesk-server/cdap/alerts.go create mode 100644 betterdesk-server/cdap/audio.go create mode 100644 betterdesk-server/cdap/clipboard.go create mode 100644 betterdesk-server/cdap/crypto.go create mode 100644 betterdesk-server/cdap/delegation.go create mode 100644 betterdesk-server/cdap/desktop.go create mode 100644 betterdesk-server/cdap/filebrowser.go create mode 100644 betterdesk-server/cdap/media_control.go create mode 100644 betterdesk-server/cdap/terminal.go create mode 100644 betterdesk-server/cdap/video.go create mode 100644 bridges/README.md create mode 100644 bridges/modbus/bridge_modbus.py create mode 100644 bridges/modbus/config.example.json create mode 100644 bridges/modbus/requirements.txt create mode 100644 bridges/rest-webhook/bridge_rest.py create mode 100644 bridges/rest-webhook/config.example.json create mode 100644 bridges/rest-webhook/requirements.txt create mode 100644 bridges/snmp/bridge_snmp.py create mode 100644 bridges/snmp/config.example.json create mode 100644 bridges/snmp/requirements.txt rename docs/{ => architecture}/BETTERDESK_CLIENT_ARCHITECTURE.md (100%) create mode 100644 docs/architecture/BETTERDESK_v3_OVERVIEW.md rename docs/{ => architecture}/CDAP_IMPLEMENTATION_PLAN.md (100%) create mode 100644 docs/architecture/CDAP_PROTOCOL.md rename docs/{ => architecture}/PORT_SECURITY.md (100%) rename docs/{ => architecture}/PROJECT_STRUCTURE.md (95%) rename docs/{ => development}/CHANGELOG.md (94%) rename docs/{ => development}/CONTRIBUTING.md (100%) rename docs/{ => development}/CONTRIBUTING_TRANSLATIONS.md (100%) rename docs/{ => development}/TRANSLATION_SUMMARY.md (89%) rename docs/{ => docker}/DOCKER_MIGRATION.md (100%) rename DOCKER_QUICKSTART.md => docs/docker/DOCKER_QUICKSTART.md (96%) rename docs/{ => docker}/DOCKER_SUPPORT.md (100%) rename docs/{ => docker}/DOCKER_TROUBLESHOOTING.md (100%) rename docs/{ => enterprise}/ENTERPRISE_ROADMAP.md (100%) create mode 100644 docs/enterprise/IMPLEMENTATION_PLAN.md rename docs/{ => features}/CLIENT_GENERATOR.md (100%) rename docs/{ => features}/CLIENT_GENERATOR_QUICKSTART_EN.md (100%) rename docs/{ => features}/CUSTOM_DEVICE_API.md (100%) rename docs/{ => features}/STATUS_TRACKING_v3.md (100%) rename docs/{ => features}/WEB_REMOTE_CLIENT_PLAN.md (100%) rename docs/{ => performance}/GPU_FIX_QUICKSTART_EN.md (100%) rename docs/{ => performance}/GPU_OPTIMIZATION_EN.md (100%) rename docs/{ => performance}/OPTIMIZATION_SUMMARY_EN.md (97%) rename docs/{ => setup}/BUILD_GUIDE.md (98%) rename docs/{ => setup}/HTTPS_SETUP.md (100%) rename docs/{ => setup}/INSTALLATION_V1.4.0.md (100%) rename docs/{ => setup}/SYNOLOGY_INSTALLATION.md (100%) rename docs/{ => setup}/UPDATE_GUIDE.md (100%) rename docs/{ => troubleshooting}/KEY_TROUBLESHOOTING.md (100%) rename docs/{ => troubleshooting}/QUICK_FIX_EN.md (100%) rename docs/{ => troubleshooting}/TROUBLESHOOTING_EN.md (100%) create mode 100644 main.go create mode 100644 sdks/README.md create mode 100644 sdks/nodejs/README.md create mode 100644 sdks/nodejs/package.json create mode 100644 sdks/nodejs/src/bridge.js create mode 100644 sdks/nodejs/src/index.js create mode 100644 sdks/nodejs/src/protocol.js create mode 100644 sdks/nodejs/src/widgets.js create mode 100644 sdks/python/README.md create mode 100644 sdks/python/betterdesk_cdap/__init__.py create mode 100644 sdks/python/betterdesk_cdap/bridge.py create mode 100644 sdks/python/betterdesk_cdap/protocol.py create mode 100644 sdks/python/betterdesk_cdap/widgets.py create mode 100644 sdks/python/pyproject.toml create mode 100644 web-nodejs/public/css/activity.css create mode 100644 web-nodejs/public/css/automation.css create mode 100644 web-nodejs/public/css/dataguard.css create mode 100644 web-nodejs/public/css/desktop-widgets.css create mode 100644 web-nodejs/public/css/help-requests.css create mode 100644 web-nodejs/public/css/inventory.css create mode 100644 web-nodejs/public/css/network.css create mode 100644 web-nodejs/public/css/reports.css create mode 100644 web-nodejs/public/css/tenants.css create mode 100644 web-nodejs/public/css/tickets.css create mode 100644 web-nodejs/public/css/tokens.css create mode 100644 web-nodejs/public/css/tutorial.css create mode 100644 web-nodejs/public/js/activity.js create mode 100644 web-nodejs/public/js/automation.js create mode 100644 web-nodejs/public/js/cdap-audio.js create mode 100644 web-nodejs/public/js/cdap-desktop.js create mode 100644 web-nodejs/public/js/cdap-devices.js create mode 100644 web-nodejs/public/js/cdap-filebrowser.js create mode 100644 web-nodejs/public/js/cdap-terminal.js create mode 100644 web-nodejs/public/js/cdap-video.js create mode 100644 web-nodejs/public/js/dataguard.js create mode 100644 web-nodejs/public/js/desktop-widgets.js create mode 100644 web-nodejs/public/js/help-requests.js create mode 100644 web-nodejs/public/js/inventory.js create mode 100644 web-nodejs/public/js/network.js create mode 100644 web-nodejs/public/js/reports.js create mode 100644 web-nodejs/public/js/tenants.js create mode 100644 web-nodejs/public/js/tickets.js create mode 100644 web-nodejs/public/js/tokens.js create mode 100644 web-nodejs/public/js/tutorial.js create mode 100644 web-nodejs/public/js/widget-plugins.js create mode 100644 web-nodejs/routes/desktop.routes.js create mode 100644 web-nodejs/routes/pages.routes.js create mode 100644 web-nodejs/routes/tokens.routes.js create mode 100644 web-nodejs/services/cdapMediaProxy.js create mode 100644 web-nodejs/services/cdapTerminalProxy.js create mode 100644 web-nodejs/views/activity.ejs create mode 100644 web-nodejs/views/automation.ejs create mode 100644 web-nodejs/views/cdap-devices.ejs create mode 100644 web-nodejs/views/dataguard.ejs create mode 100644 web-nodejs/views/help-requests.ejs create mode 100644 web-nodejs/views/inventory.ejs create mode 100644 web-nodejs/views/network.ejs create mode 100644 web-nodejs/views/reports.ejs create mode 100644 web-nodejs/views/tenants.ejs create mode 100644 web-nodejs/views/tickets.ejs create mode 100644 web-nodejs/views/tokens.ejs diff --git a/.dockerignore b/.dockerignore index 98f84c0e..88486895 100644 --- a/.dockerignore +++ b/.dockerignore @@ -32,6 +32,8 @@ betterdesk-server/_test_* # Archive (old/deprecated files) archive/ + + # Documentation (not needed in image) docs/ screenshots/ diff --git a/.github/SECURITY_AUDIT_2026-03-01.md b/.github/SECURITY_AUDIT_2026-03-01.md deleted file mode 100644 index 77c5dd1f..00000000 --- a/.github/SECURITY_AUDIT_2026-03-01.md +++ /dev/null @@ -1,676 +0,0 @@ -# BetterDesk Security Audit Report - -**Date:** 2026-03-01 -**Auditor:** GitHub Copilot (Claude Opus 4.5) -**Scope:** `betterdesk-server/` (Go), `web-nodejs/` (Node.js) - ---- - -## Executive Summary - -This audit identified **24 security findings** across the BetterDesk project: -- **Critical:** 2 ✅ FIXED -- **High:** 5 ✅ FIXED -- **Medium:** 10 ✅ 6 FIXED, 4 LOW RISK (accepted) -- **Low:** 7 (tracked for future work) - -Many security best practices are already in place (CSRF protection, session fixation prevention, timing-safe auth, rate limiting, SQL parameterization). The findings below represent remaining gaps or areas for improvement. - ---- - -## Remediation Status (2026-03-01) - -| ID | Severity | Description | Status | -|----|----------|-------------|--------| -| C-1 | Critical | Command Injection in pingHost() | ✅ Fixed: spawn() with args array + host validation | -| C-2 | Critical | Password Logging in main.go | ✅ Fixed: Write to secure file with 0600 perms | -| H-1 | High | SQL LIKE Injection | ✅ Fixed: escapeLikePattern() + ESCAPE clause | -| H-2 | High | Error Leakage in Go API | ✅ Fixed: writeInternalError() helper | -| H-3 | High | Error Leakage in Token Handlers | ✅ Fixed: Generic error messages + logging | -| H-4 | High | Path Traversal in i18n | ✅ Fixed: isValidLangCode() validation | -| H-5 | High | MD5 for Hash | ✅ Fixed: SHA256 | -| M-1 | Medium | XSS via innerHTML | ✅ Fixed: Utils.escapeHtml() + SVG sanitization | -| M-2 | Medium | parseInt NaN validation | ✅ Fixed: isNaN() checks added | -| M-3 | Medium | Weak Random in generateId | ✅ Fixed: crypto.randomUUID() | -| M-4 | Medium | Cookie httpOnly: false | ✅ Documented: Intentional for client-side i18n | -| M-5 | Medium | Trust Proxy Default | ✅ Fixed: Default changed to false | -| M-6 | Medium | Missing Content-Type Check | ✅ Fixed: requireJsonContentType middleware | - ---- - -## Critical Findings - -### C-1: Command Injection in Network Monitor (CRITICAL) - -**File:** [web-nodejs/services/networkMonitor.js](../web-nodejs/services/networkMonitor.js#L60-L64) -**Severity:** Critical -**Description:** The `pingHost` function passes user-controllable `host` parameter directly to shell command without sanitization. - -**Code:** -```javascript -// Line 60-62 -const cmd = isWin - ? `ping -n 1 -w ${timeoutMs} ${host}` - : `ping -c 1 -W ${timeoutSec} ${host}`; - -const start = Date.now(); -exec(cmd, { timeout: timeoutMs + 2000 }, (err, stdout) => { -``` - -**Impact:** An attacker who can control the `host` parameter can execute arbitrary system commands (e.g., `; rm -rf /` or `& calc.exe`). - -**Recommended Fix:** -```javascript -// Validate hostname/IP format before use -const validHostRegex = /^[a-zA-Z0-9][a-zA-Z0-9.-]{0,253}[a-zA-Z0-9]$/; -if (!validHostRegex.test(host) && !net.isIP(host)) { - return resolve({ success: false, rtt_ms: null, error: 'Invalid host format' }); -} -// Use spawn() with array arguments instead of exec() -const { spawn } = require('child_process'); -const args = isWin ? ['-n', '1', '-w', String(timeoutMs), host] : ['-c', '1', '-W', String(timeoutSec), host]; -const proc = spawn('ping', args); -``` - ---- - -### C-2: Initial Admin Password Logged to Console (CRITICAL) - -**File:** [betterdesk-server/main.go](../betterdesk-server/main.go#L165) -**Severity:** Critical -**Description:** When a random admin password is generated, it is printed to logs in plaintext. - -**Code:** -```go -// Line 163-165 -if cfg.InitAdminPass == "" { - log.Printf(" Password: %s", adminPass) -} else { -``` - -**Impact:** The password may be visible in: -- Docker logs (`docker logs`) -- systemd journal (`journalctl`) -- Log files if stdout is redirected -- CI/CD build logs - -**Recommended Fix:** -```go -// Write password to a secure file with restricted permissions instead -if cfg.InitAdminPass == "" { - passFile := filepath.Join(cfg.DataDir, ".init_password") - os.WriteFile(passFile, []byte(adminPass), 0600) - log.Printf(" Password written to: %s (delete after reading)", passFile) - log.Printf(" (password not shown in logs for security)") -} else { - log.Printf(" Password: *** (user-provided, not logged)") -} -``` - ---- - -## High Findings - -### H-1: SQL LIKE Pattern Injection in dbAdapter.js (HIGH) - -**File:** [web-nodejs/services/dbAdapter.js](../web-nodejs/services/dbAdapter.js#L629) -**Severity:** High -**Description:** The `getAllPeers` function in the new dbAdapter does NOT escape `%` and `_` wildcards in the search parameter, unlike `database.js` which does. - -**Code:** -```javascript -// Line 629 - dbAdapter.js (NO escape) -if (filters.search) { where += ' AND (id LIKE ? OR note LIKE ? OR "user" LIKE ?)'; const s = `%${filters.search}%`; params.push(s, s, s); } - -// Compare with database.js (CORRECT - with escape) -// Line 435 - database.js -const escaped = escapeLikePattern(filters.search); -sql += " AND (id LIKE ? ESCAPE '\\' OR user LIKE ? ESCAPE '\\' OR note LIKE ? ESCAPE '\\')"; -``` - -**Impact:** User can inject `%` or `_` wildcards to match arbitrary patterns (information disclosure through pattern matching). - -**Recommended Fix:** -```javascript -if (filters.search) { - const escaped = filters.search.replace(/[%_\\]/g, '\\$&'); - where += " AND (id LIKE ? ESCAPE '\\' OR note LIKE ? ESCAPE '\\' OR \"user\" LIKE ? ESCAPE '\\')"; - const s = `%${escaped}%`; - params.push(s, s, s); -} -``` - -Also apply to lines: 903, 1510, 2096, 2297, 2889. - ---- - -### H-2: Error Message Information Leakage in Go API (HIGH) - -**File:** [betterdesk-server/api/server.go](../betterdesk-server/api/server.go#L327) -**Severity:** High -**Description:** Internal error messages are exposed to API clients via `err.Error()`. - -**Code:** -```go -// Multiple locations including line 327, 359, 380, 403, 423, 494, 519, 559, 721, 738 -writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) -``` - -**Impact:** Internal implementation details, database errors, file paths, and system information may leak to attackers. - -**Recommended Fix:** -```go -func handleDbError(w http.ResponseWriter, err error, action string) { - // Log full error internally - log.Printf("[api] %s error: %v", action, err) - // Return generic message to client - writeJSON(w, http.StatusInternalServerError, map[string]string{ - "error": "Internal server error", - }) -} -``` - ---- - -### H-3: Error Leakage in token_handlers.go (HIGH) - -**File:** [betterdesk-server/api/token_handlers.go](../betterdesk-server/api/token_handlers.go#L87) -**Severity:** High -**Description:** Database and system errors are directly exposed via `http.Error(w, err.Error(), ...)`. - -**Code:** -```go -// Lines 87, 150, 175, 199, 229, 249, 258, 343, 391, 409, 465 -http.Error(w, err.Error(), http.StatusInternalServerError) -``` - -**Impact:** Same as H-2 - information leakage. - -**Recommended Fix:** Same pattern as H-2. - ---- - -### H-4: Path Traversal Risk in i18n Language Upload (HIGH) - -**File:** [web-nodejs/routes/i18n.routes.js](../web-nodejs/routes/i18n.routes.js#L134-L145) -**Severity:** High -**Description:** Language code derived from uploaded filename or body is used in file path without full validation. - -**Code:** -```javascript -// Line 137-146 -const meta = translations._meta; -const code = meta?.code || req.body.code || req.file.originalname.replace('.json', ''); - -if (!code || code.length < 2 || code.length > 5) { - return res.status(400).json({ - success: false, - error: 'Invalid language code' - }); -} - -const result = manager.saveLanguage(code, translations); -``` - -**Impact:** An attacker could potentially submit `../../../etc/passwd` as code (though length check provides some protection). The `saveLanguage` function uses `path.join(config.langDir, ${code}.json)` which may still be vulnerable. - -**Recommended Fix:** -```javascript -// Strict validation: only allow alphanumeric + dash, 2-5 chars -if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(code)) { - return res.status(400).json({ - success: false, - error: 'Invalid language code format (use: xx or xx-XX)' - }); -} -// Also validate in saveLanguage(): -const safeName = path.basename(code); // Extra protection -const filePath = path.join(config.langDir, `${safeName}.json`); -``` - ---- - -### H-5: MD5 Used for Sysinfo Hash (HIGH) - -**File:** [web-nodejs/routes/rustdesk-api.routes.js](../web-nodejs/routes/rustdesk-api.routes.js#L323) -**Severity:** High -**Description:** MD5 is used for creating content hashes, which is cryptographically weak. - -**Code:** -```javascript -// Line 323 -const hash = require('crypto').createHash('md5') - .update(JSON.stringify(sysinfo.raw_json)) - .digest('hex') - .substring(0, 16); -``` - -**Impact:** While used only for cache invalidation (not security), MD5 is deprecated and could lead to collisions. Using a deprecated algorithm in security-critical software sets a bad precedent. - -**Recommended Fix:** -```javascript -const hash = require('crypto').createHash('sha256') - .update(JSON.stringify(sysinfo.raw_json)) - .digest('hex') - .substring(0, 32); -``` - ---- - -## Medium Findings - -### M-1: XSS Risk via innerHTML in Frontend JS (MEDIUM) - -**File:** [web-nodejs/public/js/users.js](../web-nodejs/public/js/users.js#L65) -**Severity:** Medium -**Description:** User data is rendered via template literals and innerHTML without consistent escaping. - -**Code:** -```javascript -// Line 65 - users.js -tableBody.innerHTML = users.map(user => ` - - ${user.username} - ... -``` - -**Impact:** If `user.username` contains ` - - - - + + + + <% if (typeof pageScripts !== 'undefined' && pageScripts.length) { %> <% pageScripts.forEach(script => { %> - + <% }); %> <% } %> diff --git a/web-nodejs/views/layouts/main.ejs b/web-nodejs/views/layouts/main.ejs index be7d9148..5fac2a8a 100644 --- a/web-nodejs/views/layouts/main.ejs +++ b/web-nodejs/views/layouts/main.ejs @@ -11,14 +11,16 @@ - - + + <% if (!embed) { %> - + + + <% } %> <% if (typeof pageStyles !== 'undefined' && pageStyles.length) { %> <% pageStyles.forEach(style => { %> - + <% }); %> <% } %> @@ -50,6 +52,7 @@
+
@@ -60,7 +63,10 @@
- +
@@ -87,17 +93,20 @@ embed: <%= embed ? 'true' : 'false' %> }; - - - - - + + + + + <% if (!embed) { %> - + + + + <% } %> <% if (typeof pageScripts !== 'undefined' && pageScripts.length) { %> <% pageScripts.forEach(script => { %> - + <% }); %> <% } %> diff --git a/web-nodejs/views/layouts/viewer.ejs b/web-nodejs/views/layouts/viewer.ejs index 8d49ada5..5288a1a7 100644 --- a/web-nodejs/views/layouts/viewer.ejs +++ b/web-nodejs/views/layouts/viewer.ejs @@ -14,9 +14,9 @@ - - - + + + <%- body %> @@ -35,28 +35,28 @@ - + - - - - + + + + - - - - - - - - + + + + + + + + <% if (typeof pageScripts !== 'undefined' && pageScripts) { %> <% (Array.isArray(pageScripts) ? pageScripts : [pageScripts]).forEach(script => { %> - + <% }); %> <% } %> diff --git a/web-nodejs/views/login.ejs b/web-nodejs/views/login.ejs index e02ba09e..f4b7f11a 100644 --- a/web-nodejs/views/login.ejs +++ b/web-nodejs/views/login.ejs @@ -8,9 +8,9 @@ <%= _('auth.login') %> - <%= appName %> - - - + + + + + +
+ diff --git a/web-nodejs/views/remote.ejs b/web-nodejs/views/remote.ejs index 76a355a1..224c925b 100644 --- a/web-nodejs/views/remote.ejs +++ b/web-nodejs/views/remote.ejs @@ -3,15 +3,15 @@ pageScripts: ['remote'], body: ` - -
- construction - ${_('remote.wip_notice') || 'Web Remote Client is currently under development and may not work as expected. For reliable connections, please use the RustDesk desktop client.'} - construction + +
+ info + ${_('remote.beta_notice') || 'Web Remote Client — Beta. Some features may not work as expected.'} +
-
+
diff --git a/web-nodejs/views/reports.ejs b/web-nodejs/views/reports.ejs new file mode 100644 index 00000000..c5a97686 --- /dev/null +++ b/web-nodejs/views/reports.ejs @@ -0,0 +1,115 @@ +<%- include('layouts/main', { + title: typeof title !== 'undefined' ? title : 'Reports', + pageStyles: ['reports'], + pageScripts: ['reports'], + currentPage: 'reports', + breadcrumb: [{ label: _('nav.tools') }, { label: _('reports.title') }], + body: ` + + + +
+ + +
+ + +
+
+
+ devices +

${_('reports.type_devices')}

+

${_('reports.type_devices_desc')}

+
+
+ timeline +

${_('reports.type_activity')}

+

${_('reports.type_activity_desc')}

+
+
+ security +

${_('reports.type_security')}

+

${_('reports.type_security_desc')}

+
+
+ confirmation_number +

${_('reports.type_tickets')}

+

${_('reports.type_tickets_desc')}

+
+
+ lan +

${_('reports.type_network')}

+

${_('reports.type_network_desc')}

+
+
+ inventory_2 +

${_('reports.type_inventory')}

+

${_('reports.type_inventory_desc')}

+
+
+ notifications +

${_('reports.type_alerts')}

+

${_('reports.type_alerts_desc')}

+
+
+ + + + + + +
+ + +
+
+ + + + + + + + + + +
${_('reports.report_name')}${_('reports.report_type')}${_('reports.created_at')}${_('common.actions')}
+
+
+ ` +}) %> diff --git a/web-nodejs/views/tenants.ejs b/web-nodejs/views/tenants.ejs new file mode 100644 index 00000000..d9640379 --- /dev/null +++ b/web-nodejs/views/tenants.ejs @@ -0,0 +1,108 @@ +<%- include('layouts/main', { + title: typeof title !== 'undefined' ? title : 'Tenants', + pageStyles: ['tenants'], + pageScripts: ['tenants'], + currentPage: 'tenants', + breadcrumb: [{ label: _('nav.system') }, { label: _('tenants.title') }], + body: ` + + + +
+
+
business
+
+ 0 + ${_('tenants.total_tenants')} +
+
+
+
check_circle
+
+ 0 + ${_('tenants.active_tenants')} +
+
+
+
devices
+
+ 0 + ${_('tenants.total_devices')} +
+
+
+
people
+
+ 0 + ${_('tenants.total_users')} +
+
+
+ + +
+
+ search + +
+ +
+ +
+ + + + + + + ` +}) %> diff --git a/web-nodejs/views/tickets.ejs b/web-nodejs/views/tickets.ejs new file mode 100644 index 00000000..1531c208 --- /dev/null +++ b/web-nodejs/views/tickets.ejs @@ -0,0 +1,158 @@ +<%- include('layouts/main', { + title: _('tickets.title'), + pageStyles: ['tickets'], + pageScripts: ['tickets'], + currentPage: 'tickets', + breadcrumb: [{ label: _('tickets.title') }], + body: ` +
+ + + + +
+
+ 0 + ${_('tickets.stats_total')} +
+
+ 0 + ${_('tickets.stats_open')} +
+
+ 0 + ${_('tickets.stats_in_progress')} +
+
+ 0 + ${_('tickets.stats_resolved')} +
+
+ + +
+
+ search + +
+
+ + + + + +
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + +
#${_('tickets.ticket_title')}${_('tickets.status')}${_('tickets.priority')}${_('tickets.category')}${_('tickets.assigned_to')}${_('tickets.device')}${_('tickets.created_at')}${_('common.actions')}
+
+ confirmation_number +

${_('tickets.no_tickets')}

+
+
+
+ + + + + + +
+ ` +}) %> diff --git a/web-nodejs/views/tokens.ejs b/web-nodejs/views/tokens.ejs new file mode 100644 index 00000000..8f15808b --- /dev/null +++ b/web-nodejs/views/tokens.ejs @@ -0,0 +1,237 @@ +<%- include('layouts/main', { + title: _('tokens.title'), + pageStyles: ['tokens'], + pageScripts: ['tokens'], + currentPage: 'tokens', + breadcrumb: [{ label: _('tokens.title') }], + body: ` +
+ + + + +
+
+ security +

${_('tokens.enrollment_mode')}

+
+
+ + + +
+
+ + +
+
+ vpn_key +
+
0
+
${_('tokens.total')}
+
+
+
+ check_circle +
+
0
+
${_('tokens.active')}
+
+
+
+ link +
+
0
+
${_('tokens.used')}
+
+
+
+ block +
+
0
+
${_('tokens.revoked')}
+
+
+
+ + +
+
+
+ search + +
+
+ + + + +
+
+
+ + +
+
+ + +
+ + + + + + + + + + + + + + + + +
${_('tokens.token')}${_('tokens.name')}${_('tokens.status')}${_('tokens.uses')}${_('tokens.bound_peer')}${_('tokens.expires')}${_('tokens.created')}${_('common.actions')}
${_('common.loading')}
+
+ + + + + + + + + +
+ ` +}) %>