GUACAMOLE-261: Add client-side support for the SPICE protocol#1227
Open
ciroiriarte wants to merge 33 commits into
Open
GUACAMOLE-261: Add client-side support for the SPICE protocol#1227ciroiriarte wants to merge 33 commits into
ciroiriarte wants to merge 33 commits into
Conversation
Ports necouchman's apache#761, extended to match the full guacamole-server SPICE parameter set: adds pubkey, proxy, tls-port, swap-red-blue, preferred-compression, enable-audio-input, full file-transfer group, and the complete keyboard layout list (da-dk-qwerty through tr-tr-qwerty, 20 layouts). Registers "spice" in LocalEnvironment.KNOWN_PROTOCOLS (alphabetically between rdp and ssh), adds spice.json protocol form definition with 11 connection form groups (network, authentication, security, display, keyboard, clipboard, audio, file-transfer, sftp, recording, wol), and adds all corresponding PROTOCOL_SPICE translation keys to en.json. Co-authored-by: Virtually Nick <vnick@apache.org>
Adds the disable-audio-opus boolean (audio section) and its translation, matching the guacamole-server parameter that forces the legacy CELT audio codec instead of Opus.
On modern SPICE servers (which dropped CELT) disabling Opus yields raw/lossless audio, not CELT; reword the label to reflect that rather than implying CELT.
Adds the "secondary-monitors" parameter to the SPICE protocol so the maximum number of secondary monitors can be configured per connection. This is the SPICE counterpart to the parameter added for RDP in the upstream multi-monitor work (apache#1061 by @corentin-soriano); the browser-side per-monitor window handling from that PR is protocol-agnostic and applies to SPICE unchanged.
…onitors on each RDP connection.
…annot use the clientController.
… group or when multiple clients are inside.
The browser-side multi-monitor support ported from apache#1061 (by @corentin-soriano) gated the "add monitor" UI to the RDP protocol. SPICE now implements the same server-side multi-monitor protocol (secondary-monitors advertisement + multimon-layout), so allow the multi-monitor UI for SPICE connections as well. All remaining multi-monitor logic is protocol-agnostic and driven by the secondary-monitors connection parameter.
…t layout absence getOffsetX now returns the monitor's absolute left offset (symmetric to getOffsetY) instead of summing prior monitors' rendered widths, so draw and mouse offsets are correct for vertical stacks, gaps, and non-tiled arrangements. onmultimonlayout no longer closes a user-opened monitor window the moment it is absent from the layout: it tolerates a short run of transient absences (guest thrash) before closing, so a momentary 1-monitor layout no longer tears down the secondary window. Adds getMonitorsInfos() for the resize handlers.
Coalesce rapid element-resize events into a single size request (250ms debounce), cap the effective devicePixelRatio so a single monitor and the combined width stay within a sane bound, and ignore the element resize echoes that follow a server-driven display resize for a short window. Together these break the resize feedback loop that froze the main window and let the combined display settle instead of oscillating.
Expose the SPICE recording-include-clipboard parameter (server #7) in the connection schema and add its English field label, matching RDP and VNC.
…oads (#14) Expose the new file-transfer-mode connection parameter as an ENUM (none | agent | drive | both) in the SPICE connection form, with translated labels distinguishing the bidirectional shared folder from the upload-only direct-to-guest SPICE agent transfer.
…ity) Fixes finding M1 (Medium) + L2 (Low) from the SPICE security assessment (ciroiriarte/guacamole-server#21, tracked as #29). The multi-monitor windows shared a single hard-coded same-origin BroadcastChannel('guac_monitors') with no per-message provenance. With two concurrent connections open, a secondary window's keystrokes (possibly typed credentials) were injected into the other connection's guacd session and its display stream was mirrored across the connection boundary. The primary now generates a per-connection channel id, passes it to secondary windows via their route (/secondaryMonitor/:id/:channel?), and namespaces the channel as 'guac_monitors_<id>'. Every message is tagged with the id and inbound messages carrying a foreign id are dropped before dispatch (also closes L2: unauthenticated broadcast -> client.runHandler).
Image clipboard events in a session recording previously rendered as the
literal text "[Clipboard: [Binary data]]": ClipboardEventInterpreter ran a
UTF-8 text decode on every stream regardless of mimetype, and the display
service discarded the mimetype before rendering.
Handle image/* clipboard streams distinctly: keep the base64 payload and
expose it as a (lowercased-scheme) data: URL plus a byte size, instead of
forcing it through the text decode. The player now renders image clipboard
events as an inline thumbnail chip showing mimetype and size, with a
click-to-enlarge lightbox (Escape or click-out to dismiss). A searchable
synthetic label ("[Clipboard image: mimetype size]") keeps image events
matchable by the existing key-log text filter. Text clipboard events are
unchanged.
Also document, in SessionRecording.js, that onkeyevents/onclipboardevents
are delivered only on the tunnel-CLOSED path; a Blob-sourced recording
never fires them (the bundled player feeds a StaticHTTPTunnel).
Direction annotation (guest-to-client vs client-to-guest), recorded by the
server as a log instruction, is not yet surfaced during playback and
remains a follow-up.
…playback
Two follow-ups to the image-clipboard playback support.
Direction annotation: the server emits a "log" instruction immediately
before each clipboard stream recording its direction ("clipboard stream=N
direction=guest-to-client|client-to-guest ..."). SessionRecording now routes
the log opcode to the clipboard interpreter, which buffers the direction by
stream index and attaches it to the resulting clipboard event. The player
renders it as a colour-coded badge before each clipboard event (both text
and image) - amber "from guest" for data leaving the guest (the
exfiltration-relevant case) and neutral "to guest" for pastes into it.
Unannotated recordings render exactly as before.
Thumbnails: the inline image chip previously embedded the full-resolution
data URL and merely scaled it down with CSS, inflating the key-log DOM for
recordings with many or large clipboard images. The chip now renders a
canvas-downscaled thumbnail (max 96px) and shows the image's natural
dimensions, while the click-to-enlarge lightbox keeps the full-resolution
image. Thumbnail generation is idempotent, uses $evalAsync from the async
image load, and aborts in-flight loads on scope destroy.
Two Low-severity fixes from the SPICE security assessment (ciroiriarte/guacamole-server#21). L3 (CWE-20): the server-supplied `multimon-layout` JSON was fed straight through `JSON.parse` into offset/size math. A malformed payload threw out of the instruction handler loop (client teardown) and non-finite geometry poisoned the offsets with NaN. Client.js now parses and dispatches inside a try/catch and requires a non-null object; guacManageMonitor validates each entry's geometry (finite width/height/top/left, positive dimensions) and treats a malformed entry as an absent monitor. Closes #30. L4 (CWE-1230): values streamed back via `argv` for immutable arguments were reflected into the UI-visible arguments model, which could surface a sensitive parameter (e.g. a password) in the connection-parameters panel. ManagedClient now skips reflecting parameters whose name matches password/passphrase/private-key/client-key/secret/token, keeping secret material out of the UI while leaving public material (host-key, public-key, pubkey) visible. Mutation toward guacd was already rejected server-side. Closes #31.
First increment of the clipboard audit cockpit (guacclip Phase 2). Adds a dedicated "Clipboard Activity" panel to the session-recording player, alongside the existing key-log, toggled from the player controls. The panel lists clipboard transfers as chronological cards: click (or keyboard Enter/Space) seeks to the event; a text-labelled, colour-coded direction badge marks each as copied-out-of-guest (exfiltration) or pasted-in; text is shown in a code block with show-more, images as a lazy downscaled thumbnail with a click-to-enlarge lightbox; each card offers a Download of the original artifact. A header button exports the whole activity list to CSV (formula-injection-neutralized and UTF-8-BOM-prefixed for safe spreadsheet import). An empty state explains when no clipboard was recorded. The canvas-thumbnail logic is factored out of textView into a shared clipboardMediaService used by both the key-log and the new panel, with textView behaviour preserved (pending-image tracking + $destroy abort). Reviewed (0 critical/high); CSV formula-injection + BOM and a download decode guard applied from the review. Builds clean (webpack). Deferred to #34 follow-ups: seek-bar timeline tick-marks and rapid-repeat event clustering.
Second increment of the clipboard audit cockpit, completing the remaining Phase 2 items. Timeline tick-marks: each clipboard transfer is marked on the player's seek/heatmap bar, colour-coded by direction (amber copied-out / slate pasted-in), positioned by timestamp. Clicking a tick seeks to ~5s before the transfer (context lead-in); hovering shows the direction plus a thumbnail (images) or text preview. The tick layer ignores pointer events except on the 2px marks, so the seek slider stays draggable across its full width; ticks are a mouse affordance only (the panel cards remain the keyboard-accessible seek path), and positions are clamped to the bar. Event clustering: the Clipboard Activity panel collapses runs of consecutive same-direction, identical-content transfers into one card with a repeat count, time range, and expand/collapse. The flat event list is preserved, so CSV export and per-item download remain un-clustered. Truncation banner: ClipboardEventInterpreter.getIncompleteCount() reports streams that opened but never ended (truncated recording); SessionRecording passes it via a new, backward-compatible second onclipboardevents argument, and the panel shows a warning banner when transfers were recorded incompletely. Reviewed (0 critical/high); accessibility (tick tab-stop flood) and overflow fixes applied. Builds clean (webpack). Closes the timeline-ticks and clustering follow-ups on #34.
Follow-ups from a quality/security assessment of the clipboard audit cockpit. Security: image Download no longer echoes the attacker-controlled recording mimetype. imageExtension is now a closed allowlist (unknown subtypes, e.g. a forged image/svg, map to an inert .bin), and the downloaded Blob is typed application/octet-stream rather than the recording mimetype - so a hostile recording can no longer produce a script-capable file that runs when the auditor opens it. Performance: seek-bar tick tooltips now use the downscaled thumbnail (tick.raw.thumbURL) instead of the full-resolution data URL, and ticks are capped at 500 (the panel and CSV still retain every event) so a recording with thousands of clipboard transfers can't bloat the transport bar. De-duplication: the byte-identical direction-metadata maps in clipboardView and player were hoisted into clipboardMediaService.getDirectionMeta, and the verbatim-duplicated lightbox controller (open/close/Escape) was extracted into clipboardMediaService.attachLightbox, now shared by the key-log and clipboard viewers. attachLightbox also adds focus management - focus moves into the dialog on open (via $timeout, after the ng-if overlay renders) and is restored on close - closing an accessibility gap. Hardcoded English alt/aria-label strings moved into en.json. Reviewed (APPROVE, 0 critical/high/medium); builds clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the client-side half of GUACAMOLE-261: SPICE protocol support in guacamole-client / guacamole-common-js.
Contents
disable-audio-opus,preferred-video-codec,file-transfer-mode,secondary-monitors,recording-include-clipboard, etc.).multimon-layoutparsing hardening and suppression of sensitive argv parameters in the UI.Scope note
This branch also carries the GUACAMOLE-288 browser multi-monitor redesign (ported from #1061 by @corentin-soriano, original authorship preserved), with the multi-monitor UI extended to SPICE connections and per-connection
BroadcastChannelnamespacing.Testing
Validated end-to-end (browser → guacamole-client → guacd → SPICE guest) as part of the GUACAMOLE-261 QA battery (14/14): SPICE render, keyboard input with layout mapping, multi-monitor extend, and clipboard playback. Frontend builds clean.
Companion PRs: server (
apache/guacamole-server) and documentation (apache/guacamole-manual).