366 Commits

Author SHA1 Message Date
Dave Kempe 658b1c1764 v1.9.6 v1.9.6 2026-07-30 11:26:21 +10:00
Dave Kempe ce8aa1de22 docs(screenshots): refresh for multi-Vault UI + add My Credentials
Refresh Connections (credentials banner/pills, Credentials nav, larger
folder chevron), and the RDP/VDI/Web/SSH-tunnel editors, plus the
Sessions/Recordings/Reports/Admin pages (real data sanitised). Add a new
My Credentials screenshot (scroll/filter/collapse, masked fields). Drop
the stale version note. tokens_view and recordings_player_histogram are
left as their prior captures.
2026-07-30 09:49:00 +10:00
Dave Kempe ba207010b7 docs: document multi-Vault backends, per-credential scope, My Credentials UX
- configuration.md: [vault_shared]/[vault_local] backends, the DR rationale,
  user_credentials_default_scope, and the VAULT_SHARED/LOCAL_SECRET_ID env vars.
- credential-variables.md: Credentials nav link, scroll/filter/collapsible
  editor, resumable focused prompt, needed-pill + banner, and the shared/local
  per-credential scope with its outage trade-off.
- migration.md: vault-migrate runbook (dry-run, copy, cutover ordering).
- CLAUDE.md: multi-Vault backend summary under the Vault section.
2026-07-29 20:07:51 +10:00
Dave Kempe 842aeec8f1 feat(connections): persist credentials-banner dismissal
Dismissing the "N credentials to set up" banner now persists in
localStorage instead of a page-lifetime flag, so it stays gone across
refreshes. It records exactly which credentials were dismissed and
re-appears only if a genuinely new unset credential shows up — so a
partial save that shrinks the set won't re-nag, but a new requirement
still surfaces.
2026-07-29 19:40:12 +10:00
Dave Kempe 9c07249407 fix(connections): larger, clearer credential group chevron
The collapsible group caret used the small-triangle glyph (U+25BE) like
the old folder chevron did, so it read faint. Switch to the full-size
U+25BC and bump its size, matching the folder-tree chevron.
2026-07-29 19:36:05 +10:00
Dave Kempe 484b7bcb26 feat(connections): make My Credentials scale - scroll, filter, collapse
The credential editor rendered a flat list inside a modal that scrolled as
a whole, so past a few variables the Save/Close buttons and context
scrolled off and it got unusable. Restructure it: the modal header (title,
description, filter box) and the Save/Close footer are pinned and only the
credential list scrolls. Add a filter box (shown once there are more than a
handful of variables) that live-filters rows by name/domain, and group each
domain into a collapsible section. Focused single-connect mode is unchanged
(few rows, no filter box).
2026-07-29 19:31:04 +10:00
Dave Kempe eaf6525320 feat(connections): proactive credentials banner and per-entry pills
Fetch the user's set credential variables on load and cross-reference
against each entry's credential_variables (already on EntryInfo). Entries
that reference variables the user hasn't set get a "credentials needed"
pill, and a dismissible banner reports how many credentials are still to
set up (aggregated across the search index). Both link to the editor, and
refresh after a save. Turns the old reactive "connect fails, then hunt for
the menu" flow into something visible up front.
2026-07-29 18:21:10 +10:00
Dave Kempe 95e2c6d643 feat(connections): resumable credential prompt + Credentials nav link
When a connect fails for missing credential variables, open the My
Credentials editor focused on just the missing variables ("credentials
needed to connect to X") and, on save, re-run that exact connect instead
of dead-ending the user in the full editor with a toast. openMyCredentials
now takes { onlyVars, heading, onSaved }.

Also promote My Credentials to a visible "Credentials" nav link (kept in
the Settings menu too) so people can find it without hitting a failure
first.
2026-07-29 17:43:41 +10:00
Dave Kempe f5ccc35cdf feat(vault): per-credential shared/local scope for credential variables
Each per-user credential variable can live in the shared backend (fleet-
wide) or the local backend (this instance) - its location is the scope.
Reads merge both backends (local wins); writes partition by a per-key
scope sent from My Credentials, moving a key between backends when its
toggle changes and honouring "leave blank to keep". A "Shared across
sites" toggle appears per credential.

Quick-start preserved: with one Vault the shared and local cells alias the
default, creds_split() is false, the toggle is hidden, and everything is
written to the single store in one call - byte-identical to before. New
config user_credentials_default_scope (default "local") seeds new toggles.
The write partition is a pure function with unit tests covering single-
store, blank-keeps-existing, scope routing, cross-scope moves, and the
default-scope fallback.
2026-07-29 17:39:54 +10:00
Dave Kempe b8c8f55d9a feat(vault): surface unavailable scopes with a greyed tree node
list_all_folders now returns (folders, unavailable_scopes); a scope whose
dedicated backend is down is reported instead of silently dropped, and
GET /api/addressbook includes unavailable_scopes. The Connections tree
renders a non-interactive greyed "<scope> connections unavailable" node
so a partial outage is visible while other scopes stay usable; the 15s
/api/me poll refreshes it.

Single-Vault is unaffected: every scope aliases the one backend, so
unavailable_scopes is always empty while it is up (full outage still
returns 503 and the existing banner). Adds a test asserting a lone
[vault] block is sufficient (quick-start invariant).
2026-07-29 17:04:05 +10:00
Dave Kempe 93cc3493bb feat(vault): add vault-migrate subcommand for the DR split
Copies an address-book scope subtree (entries + every folder's .config,
so allowed_groups/inherit_from_parent come along) between two configured
backends, resolving each from config + its VAULT_*_SECRET_ID. Because the
scope->path layout is identical in every backend, this is a same-identity
server-to-server copy, not a path rewrite.

Flags: --scope shared|instance, --from/--to (vault|vault_shared|
vault_local), --dry-run, skip-existing by default with --overwrite, and
--users to bulk-copy users/* (documented as "makes them shared"). Exits
non-zero if any copy fails. Adds get/put_user_credentials_by_key and
list_user_keys to VaultClient for the --users path.
2026-07-29 16:14:22 +10:00
Dave Kempe a8a0a6be11 feat(vault): route address-book scopes to per-scope backends
Add optional [vault_shared] and [vault_local] config blocks so the shared
and instance scopes can each be served by a dedicated Vault, with its own
connection, retry loop, and token renewal. A bare [vault] is unchanged:
shared and local alias the default cell, so every scope resolves to the
one connection exactly as before.

VaultBackends gains scope-dispatching wrappers (get_entry, list_entries,
resolve_folder_access, ...) that route by the scope argument, plus a
fan-out list_all_folders across scopes tolerant of a down backend. Folder
listing is split into list_folders_in_scope; a new VaultError::Unavailable
marks a configured-but-unconnected backend. Per-user credential variables
and the LUKS key stay on the default backend for now.

Secret IDs: VAULT_SECRET_ID (default), VAULT_SHARED_SECRET_ID,
VAULT_LOCAL_SECRET_ID.
2026-07-29 15:53:36 +10:00
Dave Kempe 0db14a2ac6 fix(connections): use full-size triangles for the folder tree chevron
The folder expand/collapse indicator used the small-triangle glyphs
(U+25BE/U+25B8), which render as faint, hard-to-see ink even at 20px,
while every other collapsible on the page uses the full-size triangles
(U+25BC/U+25B6). Switch the folder chevron to match so it is clearly
visible.
2026-07-29 14:39:12 +10:00
Dave Kempe 52c929a882 refactor(vault): introduce VaultBackends state (single backend, no behavior change)
Wrap the single Vault connection cell in a VaultBackends struct that will
hold per-scope backends (default/shared/local). Today all three alias one
cell, so behaviour is identical; this isolates the ~20-handler type
plumbing from the upcoming multi-Vault backend split.
2026-07-29 14:19:49 +10:00
Dave Kempe ca0327eee1 fix(import): parse multi-row and multi-line SQL dumps
The Guacamole import parser scanned line by line, so it missed the
multi-row INSERT layout that modern mariadb-dump/mysqldump emit by
default (VALUES on its own line, one tuple per line). Valid dumps
reported "No connections found". Parse whole statements instead, so both
the default multi-line format and single-row dumps import correctly.
String literals, backtick identifiers, and SQL comments are skipped so
embedded semicolons and apostrophes cannot split or corrupt a statement.
2026-07-29 12:45:58 +10:00
Dave Kempe b7bdfea3a0 v1.9.5
Multi-monitor support for SPICE and RDP, plus an "open in new window" action
for address-book entries.

- SPICE multi-channel composite (Windows/PVE multi-QXL topology), main-window
  crop to monitor 0, per-monitor pop-out windows.
- RDP multi-monitor via the Display Control channel (patch 010).
- Open a connection in its own browser window from the address book.
- Client: fit-to-window on connect, and pop-outs close/notify with the session.
v1.9.5
2026-07-28 09:46:40 +10:00
Dave Kempe 8d1f6c24d7 fix(multimon): correct SPICE layout when the guest rearranges a secondary head
For a multi-channel SPICE guest (each head is a separate display channel, e.g.
a Windows multi-QXL guest), publish_layout positioned each monitor at
origin_x + the guest-reported config->x/y. But config->x/y is the guest's own
virtual-desktop arrangement, not an offset within the channel's surface: once
the guest rearranges a secondary head (which happens as soon as the primary is
resized, e.g. by fit-on-connect), it reports config->x = primary width, so the
monitor was placed at origin_x + config->x (double-counted) and clamped to a
sliver off-canvas. This is exactly where the compositor already blits the
surface (at origin_x), so publish each channel's whole surface at its origin
and ignore the guest x/y. Single-channel guests (one combined surface with
several monitor regions) still split by the reported regions.
2026-07-28 09:46:40 +10:00
Dave Kempe 928c6d4862 fix(client): fit session to window on connect + tear down pop-outs with the session
fit-on-connect: a session opened in a new window (at a fixed size), or otherwise
created at dimensions that differ from the viewing window, now fits the window
without a manual resize. The client previously only sent a size on a
window-resize event. The one-time fit is retried past the point where the resize
path becomes ready (RDP Display Control channel connected / SPICE agent and
display ready) because an early send is silently dropped; guacd coalesces the
retries into a single resize since the requested size is unchanged.

pop-out teardown: a monitor pop-out is only a satellite of the main window (it
blits from that window's canvas and sends input through its client), so it
cannot outlive it. Closing the main window now closes its pop-outs instead of
leaving a frozen orphan, and a session disconnect shows a "Disconnected" overlay
in the pop-out rather than a stale live image.
2026-07-28 09:15:27 +10:00
Dave Kempe 6cfec88f25 feat(multimon): RDP multi-monitor via the Display Control channel
Adds RDP multi-monitor, reusing the protocol-agnostic client work already used
by SPICE (secondary-monitors advertisement, multimon-layout cropping, pop-out
windows, native-mouse mapping).

guacd (patch 010): a "secondary-monitors" arg enables multi-monitor. The
Display Update module now tracks a per-monitor layout (tiled left-to-right,
top-aligned, widths rounded to RDP-valid geometry) and sends the full
DISPLAY_CONTROL_MONITOR_LAYOUT array via SendMonitorLayout rather than a single
monitor. The host extends its desktop across the monitors and streams one
combined framebuffer, so there is no client-side compositing (unlike SPICE).
guacd advertises secondary-monitors on user join and publishes the
multimon-layout so the client can split the framebuffer into per-monitor
windows.

rustguac: RdpParams gains secondary_monitors, wired from the entry's
max_monitors and sent as the secondary-monitors connect arg (resize-method is
already display-update, which the Display Control channel requires). The
address-book entry editor gains a Monitors field for RDP.

Validated against a Windows RDP host: ticking a second monitor extends the
desktop, the second head renders and resizes, and clicks land on both.
2026-07-28 08:42:23 +10:00
Dave Kempe 203d7c2cca feat(connections): "open in new window" action for address-book entries
Each entry gains a compact icon button beside Connect that launches the
session in a separate browser window (which can be dragged to another
physical display, and its secondary monitors popped out from there). Connect
is unchanged (opens a new tab).

The window is opened synchronously in the click handler so it is not
popup-blocked after the async connect fetch, then navigated once the session
exists; it also works through the credential prompt. The icon is a compact,
vertically-centred sibling that stays on one line with Connect.
2026-07-27 20:40:06 +10:00
Dave Kempe 2be5bfd849 feat(multimon): SPICE multi-channel composite + monitor-0 crop + pop-out input
guacd (patch-008): composite multiple SPICE display channels into one
framebuffer. A Windows/PVE guest exposes each head as a separate QXL device
(a separate display channel with its own primary surface); the previous code
tracked a single display channel and shared surface, so a second head
clobbered it and the session showed black or dropped. Now each channel is
tracked by id, tiled left-to-right into the combined default layer, and the
multimon layout enumerates every head with its offset. The SPICE pointer is
mapped from the combined coordinate to the owning head's local coordinate
(clamped to bounds so an edge value maps to the correct head).

client: the main window now crops to monitor 0 instead of showing the whole
combined framebuffer, and rescales to fit on resize. Secondary monitors open
in their own window that fills and rescales; that window's pointer uses native
mouse events mapped from the canvas's live rect, because Guacamole.Mouse does
not track the X axis correctly in a popup window. The per-frame blit clamps to
the source framebuffer bounds so a resize cannot read past it and produce a
corrupt image.

Validated on a Windows PVE console (VMID 300): both heads render composited,
clicks land on target on both monitors, and resizing the second monitor is
stable.
2026-07-27 20:15:57 +10:00
Dave Kempe 24e8aee5d4 feat(multimon): A3 per-monitor windows + entry storage for monitor count
client.html: per-monitor tick-boxes (contiguity-enforced) that open each
secondary monitor in its own window, blitting that monitor's region of the
combined framebuffer (rAF) and forwarding mouse (combined coords) + keyboard;
untick/close releases the monitor. Requests floor to a sane size if a popup
reports zero dimensions.

Entry storage: AddressBookEntry/EntryInfo gain max_monitors, ab_connect_entry
passes it through, and the Proxmox editor gets a Monitors field. Lets a saved
Proxmox/SPICE entry offer multiple monitors (previously only the ad-hoc API
path could).

KNOWN ISSUE: enabling multi-monitor (secondary-monitors>0) intermittently drops
the SPICE connection ~2-6s after connect, independent of the request. Under
investigation; single-monitor is unaffected. A3 end-to-end (tick -> activate ->
render) still needs a real-browser verification (Playwright can't size popups).
2026-07-27 11:52:31 +10:00
Dave Kempe 1d70b2ae24 feat(multimon): A2 controls sidebar with send-keys (Ctrl+Alt+Del)
Adds a Controls side panel (autohide-integrated, mirroring the clipboard/files
tabs) with a Send Keys section: Ctrl+Alt+Del, Ctrl+Alt+Bksp, Alt+F4, Alt+Tab,
Win, Esc, PrtSc, sent via a press-in-order/release-in-reverse key combo. Also a
Monitors section that reflects the server-advertised count (per-monitor tick-box
selection + rendering land in the next increment).

Verified on the canary vs VMID 300: Ctrl+Alt+Del triggers the Windows secure
attention screen; Monitors shows '2 monitors available'.
2026-07-27 11:19:35 +10:00
Dave Kempe c526c2f00d feat(multimon): A1 receive plumbing for multi-monitor (SPICE)
Client library (Client.js): new onmultimonlayout callback + a multimon-layout
layerPropertyHandler (mirrors multi-touch), and sendSize extended to carry the
optional per-monitor x_position/top_offset.

rustguac: SpiceParams.secondary_monitors + a secondary-monitors connect arg so
guacd advertises the allowed monitor count; CreateSessionRequest.max_monitors
(secondary = max-1) wired through both SPICE branches.

client.html: onargv reads the secondary-monitors count and onmultimonlayout
parses the layout JSON (logging for now; per-monitor windows are A2).

Verified on the canary vs VMID 300: client logs 'server allows 2 monitors' and
receives a multimon-layout. The layout is request-driven (guacd activates a
second guest head only when the client sends a size for monitor 1), so the
second monitor appears in A2.
2026-07-27 11:10:52 +10:00
Dave Kempe 3fb250dc99 docs(readme): add SPICE + Proxmox VE session types and headless API integration
Documents the new SPICE and Proxmox VE console session types (intro, architecture
diagram, and session-type table) and the headless ws-ticket API integration.
Also replaces em-dashes with colons/sentence breaks throughout the README to
match the project prose style.
2026-07-27 08:00:00 +10:00
Dave Kempe 6d1197fdc3 docs(api): document session connection flow, ws-ticket, and SPICE/Proxmox
Adds a 'Connecting to a session' section covering the owner-vs-join
distinction, the three owner-auth modes (OIDC cookie, sessionStorage key,
ws-ticket URL), POST /api/ws-ticket, and the end-to-end headless integration
recipe (mint a ticket, open /client/{id}?ticket=...). Documents the new
spice/proxmox/vdi session types and their spice_*/proxmox_* fields, and fixes
the stale share_url response example (was &key=; now ?token= with ws_url/status).
2026-07-26 08:51:24 +10:00
Dave Kempe 86cee24e63 fix(fuzz): bump russh 0.62.2 -> 0.62.4 in the fuzz lockfile
Clears Dependabot alerts GHSA-g9hv-x236-4qp3, GHSA-cqjc-rmpq-xprq,
GHSA-5xvq-cp9x-6p6r (russh pre/post-auth panics, patched in 0.62.4). Only the
fuzz harness's lockfile was affected; the shipped binary already uses russh
0.62.4 via the main lockfile, so v1.9.0 is not vulnerable.
2026-07-26 08:32:01 +10:00
Dave Kempe c675f250f5 v1.9.0 v1.9.0 2026-07-26 08:15:25 +10:00
Dave Kempe 7d9350e065 feat(client): accept a ws-ticket in the client URL for headless owner connections
The built-in client could only authenticate the owner WebSocket via an OIDC
session cookie or a sessionStorage API key. Headless API integrations have
neither, so the owner connection was rejected and guacd reported 'User is not
responding'. Now /client/{id}?ticket=<wst> is honoured: a backend mints a
single-use ticket via POST /api/ws-ticket and hands the browser a ready URL,
keeping the durable API key server-side. The metadata fetch is skipped in this
path (it needs its own auth and would consume the one-shot ticket).
2026-07-26 08:06:49 +10:00
Dave Kempe edd7d7d377 deps: bump russh 0.62.4, base64 0.23, clap 4.6.3, tokio 1.53.1, serde_json 1.0.151, tokio-util 0.7.19, libc 0.2.189
Clears dependabot #182-#188. base64 needed the direct constraint widened to
0.23 (still used for JWT decode in oidc.rs); russh 0.62.4 moves curve25519/
ed25519-dalek off release candidates onto stable. 279 tests green, clippy clean.
2026-07-26 07:22:55 +10:00
Dave Kempe cd9117936d fix(guacd): silence spice-gtk 'Invalid port value' on TLS-only SPICE (patch 009)
For TLS-only SPICE (Proxmox), rustguac sends an empty plain port so guacd
uses tls-port. guac_spice_session_configure() set the spice-gtk port property
for any non-NULL settings->port, but an omitted arg parses to an empty string,
so spice-gtk logged 'Invalid port value' per channel. Only set the port when
non-empty.
2026-07-24 19:42:23 +10:00
Dave Kempe 00a8f42572 feat(spice): Proxmox VE console connection type (node auto-detect, split token, SSH tunneling)
Split SPICE into two connection types: "spice" (direct libvirt/QEMU) and
"proxmox" (PVE console brokered via the spiceproxy API). Both produce a guacd
SPICE connection.

- Deliver the SPICE ticket/password as a connect arg instead of a post-connect
  argv stream, so it is set before guacd authenticates. Fixes an auth race that
  produced intermittent "SPICE authentication failed".
- TLS-only SPICE sends an empty plain port so guacd connects via tls-port
  rather than plaintext against a TLS endpoint.
- Proxmox node is optional: resolve it from the VM id via /cluster/resources
  (as the PVE web UI does).
- Split the PVE API token into a visible Token ID (shown in the User column)
  and a masked secret; join them as "id=secret" for the API.
- Surface the PVE response body on non-2xx (safe: only a 2xx spiceproxy
  response carries a ticket), turning opaque 500s into actionable messages.
- SSH tunneling for Proxmox: tunnel both the PVE API call and the spiceproxy
  connection through the jump-host chain in-branch. Also rewrite tls_port
  (not port) for direct-SPICE TLS over a tunnel.
- Store proxmox fields on address book entries; populate Host/User columns;
  orange Proxmox badge. Runtime dep: libspice-client-glib-2.0-8.
2026-07-24 17:42:09 +10:00
Dave Kempe 8c70a23780 feat(spice): Proxmox VE SPICE broker (backend, API-level)
Add a just-in-time Proxmox broker for SPICE consoles. PVE issues one-time,
~30s SPICE tickets via its API, so they cannot be stored; the broker fetches
the config at connect time:
- src/pve.rs: minimal PVE API client. POSTs to
  /api2/json/nodes/{node}/qemu/{vmid}/spiceproxy with an API-token header,
  parses host / proxy / tls-port / password(ticket) / ca / host-subject, and
  unescapes the CA PEM newlines. Never logs the token or ticket, and never
  puts the response body (which carries the ticket) in an error.
- session.rs: CreateSessionRequest spice_pve_* fields (host/node/vmid/token/
  verify_tls); when spice_pve_host is set, the SPICE create_session branch
  calls the broker and maps the result onto SpiceParams (hostname=host, plus
  proxy, tls, tls-port, ca-cert, cert-subject, and the argv ticket).

API-testable now (POST /api/sessions with session_type:spice + spice_pve_*).
Address-book entry storage + a Proxmox UI are the next increment.
2026-07-24 12:59:30 +10:00
Dave Kempe 4a2021bd2f feat(spice): connections.html UI for the SPICE connection type
Add SPICE to the connections entry editor: a SPICE type option, a fields
block (hostname / port / password / color-depth, plus TLS / tls-port /
ignore-cert / CA cert / cert-subject / proxy for connecting through a SPICE
proxy such as Proxmox's), and the show/hide, save, load, and clear wiring
mirroring the VNC type. connections.html is served from disk, so no binary
rebuild is needed for this file.
2026-07-24 11:10:06 +10:00
Dave Kempe b0c6958b2d feat(spice): add SPICE session type + rustguac plumbing
Wire SPICE as a first-class session type through the rustguac stack,
mirroring the VNC/RDP pattern:
- guacd.rs: SpiceParams + ConnectionParams::Spice + protocol select + arg
  mapping. SPICE credentials (password/username) are streamed to guacd via an
  argv stream after connect (send_argv), since guacd's SPICE client reads them
  from argv, not the connect args. SPICE has no width/height/dpi connect args
  (it sizes via the size instruction).
- session.rs: SessionType::Spice, CreateSessionRequest spice_* fields
  (tls/tls-port/ca-cert/cert-subject/proxy), a SPICE create_session branch,
  and tunnel host/port handling.
- vault.rs/api.rs/import.rs: AddressBookEntry + EntryInfo spice_* fields
  threaded through the connect / quick-connect / import paths.

The tls/ca-cert/cert-subject/proxy fields lay groundwork for brokered Proxmox
VE consoles. No connections.html UI yet (to follow); usable via the API.
2026-07-24 10:04:05 +10:00
Dave Kempe c2c5679cad build(spice): vendor guacd SPICE protocol support (patch 008)
Vendors native SPICE protocol support (libguac-client-spice) from upstream
PR apache/guacamole-server#688 (GUACAMOLE-261) as patch 008, on top of the
pinned guacd (6719b20d) + existing patches. Wires --with-spice and the
libspice-client-glib-2.0-dev build dep into build-deb.sh, install.sh,
Dockerfile, and dev.sh. guacclip is kept in the source but not built
(--disable-guacclip, like guacenc/guaclog); the PR's incidental non-SPICE
terminal.c keyboard change is excluded.

guacd builds green with libguac-client-spice on Debian 13 under -Werror.
rustguac-side wiring (SessionType::Spice / SpiceParams) still to come.
2026-07-24 08:52:02 +10:00
Dave Kempe 413619857c v1.8.2 v1.8.2 2026-07-23 10:10:55 +10:00
Dave Kempe 39075d8fa3 build(guacd): uplift pinned guacd 1.6.0-218 -> 1.6.0-302 (main 6719b20d)
- Bump pin 2980cf0 -> 6719b20d in Dockerfile, install.sh, release.yml,
  docs/installation.md. -Werror verified clean on the new base (the
  GUACAMOLE-2221 pin reason no longer applies).
- Drop patch 006 (terminal OSC-consume): upstreamed as GUACAMOLE-2213
  (guac_terminal_unknown_osc).
- Rebase patch 004 (H.264 display worker) onto the refactored libguac
  display internals: the queued-H.264-frame free moved into the deferred
  guac_display_free_removed_layers path.
- Patches 001/002/003/005/007 unchanged (apply clean on new base).

Local build green under -Werror (guacd + rdp/ssh/vnc). H.264 passthrough
still needs runtime verification on an xrdp+x264 target.
2026-07-23 09:38:56 +10:00
Dave Kempe 45b6d64ae9 v1.8.1 v1.8.1 2026-07-20 15:19:35 +10:00
Dave Kempe e7c515654e feat(client): per-entry auto-hide for the clipboard/files side tabs
Add an autohide_side_tabs option (Option<bool>, default off) on the address
book entry, threaded through the same path as fullscreen_on_connect
(AddressBookEntry, EntryInfo, CreateSessionRequest, Session, SessionInfo, the
API connect/quick-connect builders, and import defaults). When set, client.html
slides the left-edge Clipboard and Files tabs off screen when idle and brings
them back when the pointer nears the left edge; defaults preserve the current
always-visible behaviour. Checkbox added to the entry editor.
2026-07-20 15:19:35 +10:00
Dave Kempe 8a047c7296 fix(auth): fail closed on unparseable credential expiry
validate_api_key and validate_user_token enforced expires_at only when it
parsed as strict RFC 3339, silently ignoring any other format, so a malformed
value (e.g. "2026-12-31" or the SQLite "YYYY-MM-DD HH:MM:SS" timestamp the DB
itself writes) let the credential authenticate forever. Add parse_expires_at,
which accepts RFC 3339, ISO-without-zone, SQLite datetime and bare dates
(end-of-day UTC), and treat an unparseable value as expired. Reasonable
formats now enforce correctly rather than locking the credential out.
2026-07-20 11:36:59 +10:00
Dave Kempe 48d263b733 fix(api): require ownership for VDI container thumbnails (IDOR)
get_vdi_container_thumbnail served any container's live desktop screenshot
to any authenticated user: it took only the container name and did no
ownership check, and names are the deterministic rustguac-vdi-{user}. Add an
owner-or-admin gate mirroring get_session_thumbnail: a caller may only read a
container derived from their own username (rustguac-vdi-{user}[-{entry}]);
admins may read any. Returns 404 for non-owners so container existence is not
leaked.
2026-07-20 11:28:17 +10:00
Dave Kempe b52fd1df82 ci: bump actions/checkout v6->v7, actions/cache v5->v6 2026-07-20 11:06:34 +10:00
Dave Kempe 0d4eb24994 deps: batch bump for 1.8.1
- quinn-proto 0.11.14 -> 0.11.16 (RUSTSEC-2026-0185, remote memory
  exhaustion via reqwest; unblocks CI cargo-audit)
- russh 0.61 -> 0.62 (also drops the yanked crypto-bigint 0.7.3)
- tower-http 0.6 -> 0.7
- rustls 0.23.42, uuid 1.24.0, socket2 0.6.5, serde_with 3.21.0,
  rand 0.10.2, webpki-roots 1.0.9, toml 1.1.3, cmov 0.5.4
- fuzz lockfile synced (russh, serde_with, cmov)

Clears the open dependabot queue (#164-#180).
2026-07-20 11:03:43 +10:00
pletch 9735cb50d2 feat(rdp): make wallpaper, theming, full-window-drag configurable per entry
These three RDP visual flags were hardcoded off in guacd.rs. Expose them
as per-connection options (Option<bool>, default false) threaded through
the same path as enable_desktop_composition: RdpParams, the session
request, Vault entry + response, the API connect/quick-connect builders,
import defaults, and the connections.html entry editor (Video Performance
section). Defaults preserve existing behavior; VDI sessions stay off.

Cherry-picked from pletch/rustguac@da3cfda
2026-07-12 20:37:20 +10:00
pletch 4bcac32576 fix(rdp): force AVC420 for H.264 passthrough (fixes AVC444 color corruption)
The H.264 passthrough advertised GfxAVC444, so Windows hosts encoded with AVC444,
which splits the image across two bitstreams (luma main view + auxiliary chroma).
The passthrough only forwards bitstream[0], so the browser WebCodecs decoder
rendered a luma+chroma split — two blocks with green and magenta casts. RFX was
unaffected (separate codec path).

Set GfxAVC444 = FALSE in patches/004 (both the FreeRDP3 setter and direct-field
hunks), keeping GfxH264 = TRUE; AVC444v2 is never enabled and defaults off, so the
client now advertises AVC420-only. AVC420 carries a complete YUV420 frame the
decoder handles correctly. Verified against a Windows RDP session. README updated.

Cherry-picked from pletch/rustguac@17213e2
2026-07-12 15:54:30 +10:00
pletch f693b855dd fix(admin): show timestamps in local time instead of UTC
Backend stores timestamps as SQLite datetime('now') (UTC, no zone marker) and the
admin page printed them verbatim, so last-login/created/last-used/audit times read
as GMT. Add a localTime() helper that tags the unzoned string as UTC and renders
toLocaleString(); apply it to all full date-time cells. Date-only token columns
are left as UTC dates (localizing a 23:59:59Z expiry could roll the date a day).

Cherry-picked from pletch/rustguac@b5ea32e
2026-07-12 15:54:30 +10:00
pletch 5446fe9267 perf(net): disable Nagle (TCP_NODELAY) on rustguac's TCP sockets
Both of rustguac's socket hops carry tiny, latency-sensitive writes; under
default settings Nagle coalesces them against delayed-ACK, stalling input
and frame/sync traffic by ~40ms (up to ~200ms):

- rustguac -> guacd (apply_keepalive, covers both connect sites): forwards
  mouse/keyboard input events.
- rustguac -> browser (both TLS and plain listeners): display frames and
  H.264 sync acks. Linux propagates the option to accepted sockets, matching
  how keepalive is already applied here.

socket2 exposes this as set_tcp_nodelay().

Cherry-picked from pletch/rustguac@a11e7a2
2026-07-12 15:37:17 +10:00
pletch e23c189b63 feat(patches): port OSC-consume and RDP mod-16 fixes from fixes-1.6.0
Two guacamole-server patches ported from pletch/guacamole-server
(fixes-1.6.0), verified to apply cleanly on top of 001-005 against the
pinned base (apache/guacamole-server@2980cf0):

- 006-terminal-osc-consume: route unrecognized OSC sequences to the APC
  handler instead of reverting to echo (GUACAMOLE-2213). Fixes garbage
  output from e.g. systemd OSC 3008 context sequences.
- 007-rdp-disp-mod16: round RDP display dimensions down to mod-16 to avoid
  the green band along the bottom edge from H.264 macroblock padding;
  complements 005 (legacy bitmap path) by covering the H.264/GFX path.

The fork's SO_ERROR connect fix (GUACAMOLE-2107) is already in the pinned
base, and its GFX H.264 enablement is already covered by 004.

Cherry-picked from pletch/rustguac@cfe2c2e
2026-07-12 15:37:17 +10:00
Dave Kempe 627c05074b docs(oidc): document outbound HTTP proxy (Squid) egress 2026-07-06 20:12:44 +10:00