Releases: sol1/rustguac
v1.6.6
v1.6.6: Zombie WebSocket fix, smarter Reconnect
Zombie WebSocket fix
When a mid-path TCP drop killed the WebSocket, the Guacamole client stayed stuck in CONNECTED forever. Tab looked frozen, no overlay, mouse moved locally but clicks went into a closed socket. Upstream Client.js doesn't subscribe to tunnel.onerror / onstatechange; the Apache webapp wires those externally. Our lean client.html missed that glue. Fixed.
Reconnect actually reconnects
The Reconnect button on the disconnected overlay used to just reload the dead session URL, which walked straight back into the same overlay. It now POSTs a fresh /connect against the original Connections entry and navigates to the new session. Ad-hoc and shareToken paths fall back to /connections.html. Bonus: client.onerror now clears the thumbnail upload interval, so the secondary leak of XHR 404s against a dead session id stops the moment the overlay shows.
Other polish
- OIDC discovery errors: trailing-slash mismatches now produce a single actionable line instead of raw
openidconnectDebug output. Generic provider, not just JumpCloud. contrib/setup-xrdp-gfx.shnow adds xrdp tossl-certand normaliseskey.pemperms, fixing the FreeRDP MAC-checksum failure after a sid rebuild.- Aurora theme now applies when
[theme]is absent inconfig.toml, not just when it's present-but-empty. - New
docs/reverse-proxies.mdcovering nginx / Caddy / Apache / Traefik, including the%2F-decoding gotcha that 404s nested folder paths. Thanks @mauroparente (#105).
Dependencies
rustls-webpki0.103.13 (RUSTSEC-2026-0104: CRL-parse panic + URI excluded-subtree fix)rustls0.23.39,russh0.60.1,libc0.2.186
RustCrypto batch (aes 0.9, cbc 0.2, hmac 0.13, pbkdf2 0.13) and rand 0.10 deferred to v1.6.7 (#117).
v1.6.5
v1.6.5: RDP default to NTLM, Connections tree remembers where you were
RDP: NTLM is now the default authentication package
Rustguac previously defaulted to FreeRDP's Negotiate (Kerberos first, NTLM fallback) when an address book entry had no explicit auth_pkg. In practice almost nobody has a working KDC reachable via DNS on the jumpbox host, and the Kerberos failure mode is a silent RDP hang that looks identical to a dead network. Every new or Guacamole-imported RDP entry hit this.
As of v1.6.5, the effective default is NTLM. Existing entries with no auth_pkg set (including everything imported from Guacamole) automatically resolve to NTLM on connect. Per-entry overrides still work, and there's a new config escape hatch for environments that do run Kerberos:
[rdp]
default_auth_pkg = "kerberos" # or "negotiate", or omit for the "ntlm" defaultThe entry modal's NLA dropdown now reads "Server default (NTLM)" instead of "Default (negotiate)" so the label matches the behaviour, with an explicit "Negotiate (Kerberos first, NTLM fallback)" option added for completeness. Resolver precedence is: per-entry value, else [rdp] default_auth_pkg, else hardcoded "ntlm".
If you hit a silent 30+ second RDP hang after login on any prior version, this release fixes it.
Connections: folder expansion and selection persist across reloads
The Connections page now remembers which folders you had expanded and which folder you had selected, so reopening the page or logging back in no longer collapses the whole tree or snaps you back to the alphabetical first folder. State lives in localStorage per-browser (not synced across devices); stale entries for deleted folders self-prune on the next restore.
rustguac_connections_expanded: map ofscope|path-> true, saved on every tree toggle and on auto-expand-after-new-subfolder.rustguac_connections_selected:{scope, path}, saved on every selection change (click, create, delete, move-entry).
On page load, the top-level folders fetch chains into a restore walk that depth-sorts saved expansion keys, fetches each level's subfolders, then attempts to restore the saved selection via the now-populated cache. If the saved selection no longer exists (deleted, ACL revoked), falls back to the existing auto-select-first behaviour. All storage calls are wrapped in try/catch so private-mode / quota errors degrade silently.
Dependency notes
No Dependabot updates bundled. The only open alert (GHSA-cq8v-f236-94qc, rand 0.8.6 soundness, low) still has no patched version available, and its UB preconditions don't apply to rustguac's tracing-based logging.
v1.6.4
v1.6.4: connections audit log
Destructive and mutating actions on the Connections address book are now persisted to a new SQLite table (addressbook_audit_log) and surfaced on the Admin page under a "Connections Audit Log" section. Scope covers: create_folder, update_folder, delete_folder, create_entry, update_entry, delete_entry.
Each row captures the caller's email (or API key name for admin-key access), action, scope, folder path, optional entry name, client IP (resolved through the existing trusted-proxy logic), a small JSON details blob, and a timestamp.
What gets logged
The details blob is deliberately headline-only:
delete_folder:{"subfolders_deleted": 3, "entries_deleted": 17}create_folder/update_folder:{"allowed_groups_count": 2, "inherit_from_parent": true}create_entry/update_entry:{"type": "ssh"}delete_entry: no details.
Entry field values (passwords, private keys, hostnames, usernames, domains) and full request bodies are never logged. Audit rows live in SQLite, not in Vault, so logging content would pull Vault-only secrets onto disk and break the credentials-only-in-Vault design.
API
New admin-only endpoint GET /api/admin/addressbook-audit?limit=100&email=<filter> (capped at 1000 rows). Same shape as the existing GET /api/admin/token-audit.
The retention sweeper (cleanup_old_audit_log) now prunes both audit tables on the same retention window.
Dependency notes
No Dependabot updates bundled. The only open alert (GHSA-cq8v-f236-94qc, rand 0.8.6 soundness, low) still has no patched version available, and its UB preconditions don't apply to rustguac's tracing-based logging.
v1.6.3
v1.6.3: folder permission inheritance, import ACL, recursive delete fix
New
Folder permissions can flow down the tree. Each folder config grows an inherit_from_parent flag. When on, if a folder's own allowed_groups doesn't match the caller, the check walks up the slash-separated path and evaluates each ancestor. Admins still bypass. Inheritance stops at any folder with the flag off, so you can lock down a subtree independently of its parent. Default is false on existing configs (via #[serde(default)]) so nothing about current deployments changes until an admin ticks the box. New subfolders created through the UI default to checked; new top-level folders and edits to existing folders preserve the stored value.
The UI gets a matching "Inherit permissions from parent folder" checkbox in the folder create/edit modal.
Guacamole import picks up --allowed-groups. Applies an OIDC group list to the root import folder; every auto-created subfolder is written with inherit_from_parent: true so the whole imported tree picks up the same ACL without per-folder edits. Typical invocation for a migration where every group in the Guacamole tree should be visible to the same OIDC group:
rustguac import-guacamole \
--file guacamole_db.sql \
--folder Imported \
--allowed-groups 'my-oidc-group' \
--scope shared
Fixed
Silent folder delete post-v1.6.0. delete_folder in vault.rs only cleared entries and .config at the named folder. Before v1.6.0 there were no subfolders so it did the job; after subfolders became first-class it left orphan .config keys behind. The UI would refresh after delete, list_children would still find the orphans, and the folder appeared stuck. delete_folder now BFS-walks the whole subtree; the endpoint reports (subfolders_deleted, entries_deleted) counts back to the UI, and the confirmation prompt explicitly says "AND all its subfolders" when the target has children.
list_credential_variables missed subfolder entries. The scan walked only top-level folders, so any variable reference inside a subfolder (i.e. everything imported from Guacamole post-v1.6.0) stayed invisible on the My Credentials page. The scan now recurses.
Dependency notes
No Dependabot updates bundled. The only open alert (GHSA-cq8v-f236-94qc, rand 0.8.6 soundness, low) has no patched version available, and its UB preconditions don't apply to rustguac's tracing-based logging.
v1.6.2
v1.6.2: bug fixes from internal testing
Three bugs surfaced while shipping rustguac onto a fresh internal jumpbox. No user-facing reports, no security impact, just things that would bite any new deployment.
Fixes
-
Readonly sqlite DB on fresh install. Running
rustguac add-adminundersudobefore starting the service left the DB file owned byroot:root; therustguacservice user then couldn't write, surfacing later asattempt to write a readonly databaseon the first OIDC login.init_dbnow auto-chowns the DB file (and any-wal/-shmsidecars) to match the parent data-dir owner when invoked as root, making sudo-run CLI commands idempotent regardless of who runs them. -
Guacamole import flattened group hierarchy. The
import-guacamolecommand was collapsing Guacamole connection groups into hyphen-joined entry names and writing everything into a single folder. It now preserves the full group tree as real Vault subfolders (arbitrary depth, tested to 5 levels), sanitises each path segment individually, writes a.configfor every ancestor so empty parents still render in the Connections tree, and dedups entry names per-folder rather than globally. -
Malformed
config.tomlsilently ignored. A TOML parse error (e.g. a bare unquoted path) used to emit atracing::warnbefore the subscriber was installed and silently fall back to built-in defaults. Operators would see a clean startup, wonder why nothing worked, and have no log signal pointing at the cause. Errors now print viaeprintln!with thetomlcrate's column-pointed snippet andexit(1)whenever a config path was explicitly given (or the default/opt/rustguac/config.tomlexists but fails to parse).
Dependency notes
No Dependabot updates bundled. The only open alert (GHSA-cq8v-f236-94qc, rand 0.8.6 soundness, low) has no patched version available, and its UB preconditions (a log-based custom logger calling rand::rng() from a hot path) don't apply to rustguac's tracing-based logging.
v1.6.1
v1.6.1 — Security audit + test harness
Small patch release from a fresh adversarial security review of the v1.6.0 changes. No Critical or High findings; two Medium defence-in-depth gaps closed, and the test suite grew from 150 to 215 tests.
Security hardening
- Shadow tokens now audit every use, not just the mint. Previously an admin-minted shadow token was logged at
POST /api/sessions/{id}/shadowand then silently reusable for ten minutes. Each reuse now writes ashadow_usedentry totoken_audit_logwith the connecting IP and issuer, so a leaked or over-shared URL is visible after the fact. - OIDC
groupsclaim is bounded. A misconfigured or compromised IdP could previously bloat theseen_groupsSQLite table unbounded. The parser now caps each group name at 256 bytes (UTF-8 boundary preserved) and the array at 64 entries — realistic AD deployments sit well under this. .cargo/audit.tomlformalises the existingrsa(RUSTSEC-2023-0071) andrustls-pemfile(RUSTSEC-2025-0134) advisory ignores with rationale in-tree. New advisories on either crate still surface incargo audit.- CSWSH Origin/Host compare is now case-insensitive. DNS is canonically case-insensitive; the prior exact-string compare could reject legitimate traffic in rare browser/proxy combinations. Posture unchanged — a malicious Origin still has to match the Host hostname.
Test harness: 150 → 215 tests
Targeted regression coverage for the security invariants that matter:
- Shadow tokens: owner/shadow/invalid/expired matrix, IDOR guard (token for session A must not validate on B),
expires_at == nowboundary, multi-shadow dispatch, owner-wins-over-collision. - Vault path validation: traversal (
../,\, encoded forms), NUL / control / unicode / homoglyph rejection, empty and overlong names, nested paths, leading/trailing slashes. - OIDC groups: missing claim, malformed JWT, array/string normalisation, non-string filtering, 64-cap truncation, 256-byte UTF-8-safe truncation.
- Guacamole protocol parser: length overflow, non-numeric/negative lengths, malformed elements, UTF-8 mid-character split, streaming byte-by-byte delivery, 1 MiB buffer cap, embedded semicolons inside length-bounded elements.
- VDI username sanitization: unicode drop, collision resistance, shell-metachar blacklist on the resulting container name, leading/trailing dash trim.
- Recording / JPEG: extension case-sensitivity, NUL bytes, hidden double-dot, empty filename; JPEG magic rejects PNG/GIF/PDF/HTML and off-by-one-byte variations.
- Origin/Host matcher: subdomain attack, prefix-suffix attack, port stripping, case-insensitive hostname, trailing slash tolerance, missing-header pass-through.
- End-to-end async: SessionManager mint-then-validate,
mint_shadow_tokenprunes expired entries,disconnect_viewersaturating decrement, shadow IDOR across sessions,upsert_seen_groupsdedup + parameterised-query safety under hostile input. - Rate-limiter plumbing:
tower::ServiceExt::oneshotburst against a governor layer asserts both 200 and 429 — guards against a future refactor dropping.layer(GovernorLayer::new(...))from a route group.
Behaviour-preserving refactors to support testing: check_share_token_match extracted from validate_share_token, is_jpeg_magic from the thumbnail PUT handler, origin_host_matches from ws_handler.
No breaking changes
All public APIs unchanged. Config file format unchanged. No migrations. Drop-in upgrade from v1.6.0.
v1.6.0
v1.6.0 — Connections rework, shadow sessions, subfolders, per-entry sharing
The biggest release since 1.5.0. The "Address Book" is now Connections, active sessions are a real part of the Connections page, admins can shadow live sessions, share is per-entry, folders nest into subfolders, and the whole UI runs off a single shared stylesheet.
Address Book → Connections
- The page is renamed: Connections in the nav,
/connections.htmlon disk, with a permanent 308 redirect from/addressbook.htmlso existing bookmarks keep working. - API paths (
/api/addressbook/...), Rust types, and Vault storage paths are unchanged — internal churn avoided.
Subfolder support (#101)
- Folders can now contain subfolders (e.g.
Clients/Acme/Servers). Vault KV v2 paths were already hierarchical; the frontend just needed to catch up. - New tree sidebar with lazy-loaded children, expand chevrons, and a + subfolder button under each selected folder.
- Scope is now shown as an icon with hover tooltip: ⊕ for shared, ▣ for instance-only.
Session visibility (#102)
/api/sessionsnow scopes to the caller's own sessions by default. Admins (and only admins) can pass?all=trueto see everyone — used by the Sessions management page.GET /api/sessions/{id}and the thumbnail GET/PUT endpoints are owner-or-admin; they return 404 to other callers so session existence doesn't leak.- The Connections Active Sessions strip is always owner-scoped — admins manage everyone's sessions via the Sessions page instead.
Admin-only Shadow sessions
- New Shadow action on the Sessions page lets admins join any active session as a read-only viewer, without the user having to share first.
POST /api/sessions/{id}/shadowmints a short-lived (10 min) token, hashed in memory on the session, and writes an audit entry totoken_audit_log.- Shadow tokens are accepted by the existing viewer path alongside the owner's share token.
- The Sessions page now also has a count header ("12 active · 3 yours") and colour-cues the Owner column so admins can eye-scan own vs others' sessions.
Share moves to Connections
- Every active session card in Connections now has a Share and Terminate button. Share opens a themed modal with the URL, a Copy button, and a warning banner about what the URL grants.
- The old Share column on the Sessions page is gone — sharing belongs with the user's own sessions.
Per-entry sharing opt-in
- Sharing is now off by default on saved entries. Admins tick Allow session sharing on each entry in the Connections modal to let users generate share URLs for sessions launched from that entry.
- Ad-hoc sessions (POST
/api/sessionswith noaddress_book_entry) still default shareable, so external API-key callers that relied on the share URL in the response are unaffected.
Folder allowed_groups picker
- The comma-separated text input is replaced with a proper chip-based multi-select and a themed autocomplete dropdown.
- The dropdown is populated from the union of configured role-mappings and groups seen in any OIDC login claim (new
seen_groupsSQLite table, upserted on login). - Keyboard-nav with ↑/↓/Enter, custom groups still accepted via free typing.
Auto-open single entry (#103) — beta
- New per-entry Auto-open when this is the user's only entry checkbox. On fresh login, if the calling user sees exactly one entry and this flag is set, Connections navigates straight into the session. Aimed at single-purpose/kiosk accounts.
- Same-tab navigation (browsers block
window.openwithout a user gesture) — returning to Connections is via the new 🏠 Home button inclient.html's Ctrl+Alt+Shift panel. - Beta: the trigger logic is simple and conservative; a few edge cases (sessionStorage reset, subfolder entries) may still need tweaks. Turn the flag off per entry if it misbehaves.
Client Home button
client.html's Ctrl+Alt+Shift panel header has a new 🏠 Home button next to the close×. One click takes the user back to Connections — primary escape hatch for the auto-open feature above, but useful in any session.
UI overhaul: shared stylesheet
- Every static page now links a single
/rustguac.css. About 700 lines of duplicated CSS removed across the 8 pages. - Consistent 8px baseline grid for chrome controls, a 38/44/54px button ladder, uppercase letter-spaced section labels, zebra-striped tables, active-nav underline bar.
- Active nav link gets a 2px underline bar instead of just a weight change. Buttons unify into primary / accent (Connect) / ghost (+ buttons) / small (chrome links).
- Modal checkboxes no longer render as 44-px-tall blocks (side effect of the generic input rule) — proper inline alignment with their label text across every modal.
- Share modal's caution text is now a warning banner with ⚠ and
--status-pendingcolour.
Dependencies
softprops/action-gh-releasev2 → v3 (CI action, Node 24 runtime — #104)
Upgrade notes
- Bookmarks to
/addressbook.htmlwill 308-redirect to/connections.html. Automated tooling that follows redirects works without change. If you have a tool that doesn't follow redirects, update to the new URL. - Existing entries get
allow_sharing = falseby default — users that previously generated share URLs for saved entries will see the Share button disappear until an admin opts that entry in. Ad-hoc API-key-driven sessions are unaffected. - Active-session visibility: non-admin users no longer see other users' active sessions in Connections. Admins still see everything via the Sessions page.
- Shadow is admin-only and always audited — every mint writes to
token_audit_logwith admin, session id, owner, client IP, and expiry. - Auto-open (#103) is beta — if the flag misbehaves on login, untick Auto-open when this is the user's only entry on the offending entry.
Thanks
Much of this release came straight from community reports. Thanks to:
- @mauroparente — for raising the folder-hierarchy request in #101, which shaped the subfolder tree work.
- @Napsty (Claudio Kuenzler) — for reporting both the cross-user active-sessions leak (#102) and the single-entry auto-connect idea (#103). Two separate issues filed minutes apart and both landed in this release.
Keep the reports coming — open an issue with a screenshot / repro and we'll take a look.
v1.5.5
v1.5.5 — Default theme, docs cleanup, dependency fix
Default theme changed to Aurora
The default theme is now aurora (midnight blue with ambient glow) instead of dark. Existing users who haven't set a theme in their config will see the new default. Users who have selected a theme via the UI theme switcher are unaffected (their choice is stored in localStorage).
All 8 built-in presets: aurora (default), dark, light, high-contrast, terminal, nord, corporate, jaguar.
Documentation cleanup
- Theme docs updated — all 8 presets listed (was missing jaguar and aurora), added missing VDI session type badge colour fields (
type_vdi_bg/type_vdi_fg) - Installation guide — added Vault/OpenBao address book as a recommended post-install step. The address book is the primary user-facing feature and requires Vault to function.
Security dependency update
- rustls-webpki 0.103.10 → 0.103.12 — fixes RUSTSEC-2026-0098 (URI name constraint bypass) and RUSTSEC-2026-0099 (wildcard name constraint bypass)
Upgrade notes
No breaking changes. The theme default change is cosmetic only — set preset = "dark" in your [theme] config to keep the previous default.
v1.5.4
v1.5.4 — Security audit fixes + session limits
Findings from an OWASP Top 10 2025 security audit, plus session resource management.
Security fixes (7 findings)
-
Vault path traversal on reads — folder names are now validated on
get_folder_config,list_entries, andget_entry(write operations already validated). A crafted folder name with../could traverse the Vault KV path structure. -
SSH host key: reject on parse error — if a stored host key is malformed, the connection is now rejected instead of silently accepting without verification.
-
Config secret redaction —
OidcConfigandVaultConfignow use customDebugimplementations that redactclient_secret,role_id, andclient_key. Previously these could appear in debug-level logs. -
Branding XSS prevention —
site_titleandlogo_urlconfig values are now HTML-escaped before injection into page templates, preventing stored XSS via configuration. -
WebSocket Origin validation — WebSocket upgrades now validate the
Originheader against theHostheader hostname to prevent cross-site WebSocket hijacking (CSWSH). Proxy-safe: compares hostnames only, ignoring ports. -
VDI bind mount hardening — home directory bind mounts now include
nosuid,nodevoptions to prevent setuid binary attacks from within containers. -
Recording access restricted —
GET /api/recordingsandGET /api/recordings/{name}now requirepoweruseroradminrole. Previously any authenticated user (including viewers) could list and download all session recordings.
Session limits and cleanup (#99)
max_sessions(default: 500) — global cap on concurrent sessions. Set to 0 for unlimited.max_sessions_per_user(default: 50) — per-user cap. Set to 0 for unlimited.- Completed session cleanup — background reaper removes completed/error/expired sessions from memory after
session_cleanup_delay_secs(default: 300s). Session history in SQLite is not affected.
Upgrade notes
No breaking changes. New config options use generous defaults. Recording access change may affect viewer and operator role users who previously had access to recordings — they now need poweruser or admin role.
v1.5.3
v1.5.3 — Security hardening
Findings from an internal security audit (OWASP Top 10 2025 + Rust-specific patterns).
Security fixes
-
Share URL redaction — session share tokens are no longer exposed to all authenticated users. Previously any user (including viewers) could list all sessions and obtain share URLs to join sessions they didn't create. Share URLs are now only returned to the session creator and admins.
-
OIDC login rate limiting — the
/auth/loginand/auth/callbackendpoints are now always rate-limited (1/sec, burst 5 per IP) regardless of the globalrate_limitconfig. Prevents brute-force attacks on the OIDC login flow. -
Disconnect instruction boundary fix — the guacd disconnect detection now checks instruction boundaries instead of raw substring matching. Previously, clipboard content or typed text containing
10.disconnect;could falsely trigger VDI container destruction. -
sudoers chown restriction — the LUKS drive mount sudoers rule now restricts
chowntorustguac:rustguaconly, preventing arbitrary ownership changes on the mount point.
Dependency updates
- tokio 1.51.0 → 1.51.1 (semaphore + net fixes)
- russh 0.59.0 → 0.60.0 (rand 0.10 internal update)
- bollard 0.18.1 → 0.20.2 (Docker API client, required VDI driver API updates)
Known advisories
rsacrate RUSTSEC-2023-0071 (Marvin Attack, medium) — transitive dependency from russh and openidconnect, no fix available upstream. Not directly exploitable in rustguac's usage.
Upgrade notes
No config changes required. The share URL change is backward compatible — session creators and admins see share URLs as before; other users no longer see them in API responses. The sessions page UI hides the share button for sessions you didn't create.