rustdesk / rustdesk/rustdesk-server
hbbs writes client-style config file to data directory via Docker bind mount
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Rust
- Estrellas
- 10.4k
- Forks
- 2.6k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Descripción
Summary
When hbbs (RustDesk rendezvous server) starts inside a Docker container with a bind-mounted data directory (./data:/root), it inadvertently creates a ~/.config/rustdesk/RustDesk.toml file inside the data directory. This file is the same client-side config format used by RustDesk desktop/mobile clients and should not exist in the server's working directory.
The file is created by hbb_common (a shared library used by both client and server), specifically through Config::get_key_pair() being called at startup via a fallback path in get_uuid().
Affected Component
- Binary:
hbbs(rendezvous server) - Not affected:
hbbr(relay server) — does not usehbb_common::Configstartup chain that triggers config write - Library:
libs/hbb_commonin bothrustdesk-serverandrustdeskrepos
Environment
- Deployment: Docker with
volumes: ./data:/root(bind mount) - Host path affected:
/root/docker/rustdesk/data/.config/rustdesk/RustDesk.toml - Permissions:
0o600(owned by root) - Container user: root
Root Cause Analysis
Trigger chain
The entry point is check_software_update() in the hbbs main thread, which spawns a worker thread. That worker calls version_check_request(), which invokes get_fingerprint() — and FingerprintingInfo::new() accesses Config::get_id(), which triggers the CONFIG lazy_static initializer (and thus Config::load()).
hbbs main() [src/main.rs:10]
├─ init_args() [src/common.rs:56]
├─ check_software_update() [src/common.rs:193]
│ └─ thread: check_software_update_() [src/common.rs:201]
│ └─ version_check_request(...) [hbb_common/src/lib.rs:425]
│ └─ fingerprint::get_fingerprint(None, None) [hbb_common/src/lib.rs:434]
│ └─ FingerprintingInfo::new() [hbb_common/src/fingerprint.rs:198]
│ └─ Config::get_id() [hbb_common/src/fingerprint.rs:203]
│ └─ CONFIG.read() TRIGGERS lazy_static!
│ └─ Config::load() [hbb_common/src/config.rs:53]
│ ├─ decrypt_str_or_original(password)
│ ├─ decrypt_str_or_original(enc_id)
│ ├─ get_auto_id() [MAC succeeds in Docker]
│ │ └─ config.id = "auto_id_str"
│ │ └─ store = true
│ └─ config.store() [hbb_common/src/config.rs:573]
│ ├─ encrypt_str_or_original(config.id)
│ │ └─ encrypt()
│ │ └─ symmetric_crypt()
│ │ └─ get_uuid()
│ │ └─ Config::get_key_pair().1
│ │ ├─ Config::load_<Config>("") [line 893]
│ │ ├─ key_pair empty → gen_keypair()
│ │ ├─ spawn background thread:
│ │ │ CONFIG.write() → set key_pair → store()
│ │ └─ return pk
│ └─ Config::store_(&config, "") WRITE RustDesk.toml
│
└─ RendezvousServer::start(...) [src/main.rs:35]
└─ get_server_sk(key) [src/rendezvous_server.rs:1229]
└─ crate::common::gen_sk(0) [src/common.rs:107]
└─ reads id_ed25519 (relative path)
Why key_pair is written separately
Config::get_key_pair() operates independently of gen_sk(). It:
- Locks
KEY_PAIRcache — misses - Loads
~/.config/rustdesk/RustDesk.tomlviaConfig::load_("") - Finds
key_pair.0empty → generatessign::gen_keypair() - Spawns a background thread that acquires
CONFIG.write()and callsconfig.store()again with the populated keypair
Result: two keypair storage mechanisms coexist independently:
| Mechanism | Path | Trigger | Write time |
|---|---|---|---|
gen_sk() (server-native) |
./id_ed25519 |
RendezvousServer::start() |
+1.1ms |
Config::get_key_pair() (shared lib) |
~/.config/rustdesk/RustDesk.toml |
check_software_update() → T1 |
+0ms |
Why the path lands in the data directory
On Linux, directories_next::ProjectDirs::from("", "", "RustDesk") resolves to:
/root/.config/rustdesk/RustDesk.toml
Because Docker is started as root with volumes: ./data:/root, the host path ./data/.config/rustdesk/RustDesk.toml is created.
The patch() function at config.rs:400 only redirects /root to /home/<user> when the effective user is not root — but inside the container the EUID is 0, so the redirect never triggers.
Why get_uuid() calls get_key_pair() on the server
get_uuid() at lib.rs:300 is a cross-platform machine identifier used by password security and fingerprinting:
pub fn get_uuid() -> Vec<u8> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(id) = machine_uid::get() {
return id.into();
}
Config::get_key_pair().1 // fallback — client code, not server code
}
On Linux containers, machine_uid::get() reads /etc/machine-id or /var/lib/dbus/machine-id, which may not exist or may be inaccessible. When it fails, the fallback Config::get_key_pair().1 executes — invoking the client-side config system unnecessarily in a server context.
Notice the timestamp ordering from container logs confirms the call sequence:
[2026-05-13 07:55:40.524858 +00:00] INFO [libs/hbb_common/src/config.rs:895] Generated new keypair for id:
[2026-05-13 07:55:40.525990 +00:00] INFO [src/common.rs:121] Private key comes from id_ed25519
[2026-05-13 07:55:40.525997 +00:00] INFO [src/rendezvous_server.rs:1251] Key: SR5mXVPV7z5NzH3lFeOio0TRzJve4YF9iQTdR4fsgAA=
[2026-05-13 07:55:40.526001 +00:00] INFO [src/peer.rs:84] DB_URL=./db_v2.sqlite3
get_key_pair() (via check_software_update thread) fires first, followed by gen_sk() (via RendezvousServer::start). The ~1ms gap matches thread scheduling.
File Contents
data/.config/rustdesk/RustDesk.toml contains the encrypted ID and keypair:
enc_id = '00ZR49a5Jx88J4wzF7ZUQq6a1AFuW3EV7psg=='
password = ''
salt = ''
key_pair = [[123, 14, 63, ...], [117, 255, 245, ...]]
key_confirmed = false
[keys_confirmed]
Code References
| File | Line | Description |
|---|---|---|
libs/hbb_common/src/lib.rs |
300-305 | get_uuid() fallback to get_key_pair() |
libs/hbb_common/src/config.rs |
893-906 | Config::get_key_pair() — loads + generates keypair |
libs/hbb_common/src/config.rs |
899-903 | Background thread writes config via config.store() |
libs/hbb_common/src/config.rs |
500-513 | store_path() → confy::store_path_perms() (0o600) |
libs/hbb_common/src/config.rs |
615-637 | Config::path() — directories_next::ProjectDirs resolution |
libs/hbb_common/src/config.rs |
400-427 | patch() — /root → /home/<user> redirect (skipped for root) |
libs/hbb_common/src/config.rs |
580-584 | Config::store_() — stores after encryption |
src/common.rs |
107-155 | gen_sk() — separate keypair mechanism for server |
src/common.rs |
130 | sign::gen_keypair() — different from Config path |
Impact
- Unexpected file in data directory —
RustDesk.tomlis a client config format, confusing for server operators - Potential key confusion — two keypair storage locations exist (
id_ed25519vsRustDesk.toml), potentially diverging - Permissions — file created with
0o600, owned by root (expected for root container) - Data pollution — this file will appear in any backup/sync of the data directory
Suggested Fixes
Option A: Fix get_uuid() to not depend on Config::get_key_pair() in server context (recommended)
The get_uuid() fallback is designed for client machines where machine_uid may be absent. On a server binary, this fallback is inappropriate. Replace the fallback with a server-appropriate UUID source (e.g., generate once from a static salt + build info):
// libs/hbb_common/src/lib.rs
pub fn get_uuid() -> Vec<u8> {
if let Ok(id) = machine_uid::get() {
return id.into();
}
// Server context: use build-derived static UUID, not Config keypair
crate::get_server_uuid()
}
Option B: Make Config::path() server-aware
Add a server-mode check that redirects ~/.config/rustdesk/ to the current working directory or an explicit server-specific path. Could be triggered by an environment variable like RUSTDESK_SERVER=1.
Option C: Skip Config::load() for server builds
The server binary does not need Config::load() behavior at startup. A compile-time #[cfg(feature = "server")] guard could skip the get_key_pair() fallback in get_uuid() entirely.
Reproduction
- On a Linux host, create an empty
data/directory docker run --rm -v $(pwd)/data:/root ghcr.io/rustdesk/rustdesk-server:latest hbbs- Observe:
data/.config/rustdesk/RustDesk.tomlis created - Check container logs — the
Generated new keypair for id:log line appears
Version References
| Component | Version / Commit |
|---|---|
| rustdesk-server repo | 815c728837b8a091c9feeeabb423d543be3a7f8d |
| hbb_common submodule | 83419b6549636ee39dacef7776c473f5802e08d6 |
| Docker image (investigated) | ghcr.io/rustdesk/rustdesk-server@sha256:10818ec05b179039c6660f4d8e74b303f0db2858bbad2b18e24992ea22d54cd6 |
Report generated: 2026-05-13
Investigated by: AI Agent
Repos examined: rustdesk-server (local clone), rustdesk (local clone, pre-pulled)
(Generated by AI Agent)
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza con libs/hbb_common/src/lib.rs, especialmente get_uuid(), y sigue su fallback hasta Config::get_key_pair() en libs/hbb_common/src/config.rs. Compara esa ruta con src/common.rs:107-155 y la cadena de inicio de hbbs a través de check_software_update(). Reproduce el comportamiento con el bind mount de Docker documentado y verifica que hbbs ya no cree .config/rustdesk/RustDesk.toml, conservando al mismo tiempo el comportamiento de su clave de servidor.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- docker, rust
- Área
- backend, devops
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 42/100