Skip to content

Add guacamole idle timeout detection and upgrade to 1.6#3017

Merged
kevoreilly merged 3 commits into
kevoreilly:masterfrom
josh-feather:guac-a-mole-idle-timeout
May 15, 2026
Merged

Add guacamole idle timeout detection and upgrade to 1.6#3017
kevoreilly merged 3 commits into
kevoreilly:masterfrom
josh-feather:guac-a-mole-idle-timeout

Conversation

@josh-feather
Copy link
Copy Markdown
Contributor

Idle timeout:

  • Add SessionTimeoutManager (web/guac/timeout_manager.py) which tracks last mouse/keyboard activity and signals analysis completion by creating the cape-<task_id> folder on the guest via the CAPE agent — the same mechanism used by the End Session button.
  • Add guac_utils.py with is_user_activity(), which matches mouse/keyboard Guacamole protocol instructions using a regex against the wire format.
  • Update consumers.py to initialise a SessionTimeoutManager per session, update activity on every inbound message, and run a monitor_timeout() background task that fires handle_timeout() when the idle threshold is exceeded. The timeout sends a Guacamole error frame (code 522) to the client before closing.
  • Add _close_websocket() to prevent double-close race conditions that were producing stack traces when the reader task and disconnect() both tried to close simultaneously.
  • Allow 'pending' tasks as well as 'running' in ACTIVE_GUAC_TASK_STATUSES so sessions can connect during the brief window before a task starts.
  • Add idle_timeout_seconds and activity_check_interval config options to web.conf.default; both default to disabled (0).
  • Add tests: test_guac_consumers.py, test_guac_timeout_manager.py, test_guacamole_activity_detection.py.

Guacamole 1.6 upgrade:

  • The previously bundled 1.4.0 client was behind what the upstream CAPEv2 installer deploys; this aligns the bundled JS with 1.6.0 for faster rendering.
  • Add guacamole-1.6.0-all.min.js static asset.
  • Update guac/index.html and analysis/overview/_playback.html to reference the 1.6.0 build.
  • Refactor guac-main.js from a procedural function to an ES6 class (GuacSession). Extracts keyboard, mouse, clipboard, scaling and error handling into dedicated setup methods. Differentiates error codes 514, 515, and 522 for cleaner user-facing messages. Uses sendMouseState with the Guacamole 1.6 fromServer flag.

Idle timeout:
- Add SessionTimeoutManager (web/guac/timeout_manager.py) which tracks
  last mouse/keyboard activity and signals analysis completion by creating
  the cape-<task_id> folder on the guest via the CAPE agent — the same
  mechanism used by the End Session button.
- Add guac_utils.py with is_user_activity(), which matches mouse/keyboard
  Guacamole protocol instructions using a regex against the wire format.
- Update consumers.py to initialise a SessionTimeoutManager per session,
  update activity on every inbound message, and run a monitor_timeout()
  background task that fires handle_timeout() when the idle threshold is
  exceeded. The timeout sends a Guacamole error frame (code 522) to the
  client before closing.
- Add _close_websocket() to prevent double-close race conditions that were
  producing stack traces when the reader task and disconnect() both tried
  to close simultaneously.
- Allow 'pending' tasks as well as 'running' in ACTIVE_GUAC_TASK_STATUSES
  so sessions can connect during the brief window before a task starts.
- Add idle_timeout_seconds and activity_check_interval config options to
  web.conf.default; both default to disabled (0).
- Add tests: test_guac_consumers.py, test_guac_timeout_manager.py,
  test_guacamole_activity_detection.py.

Guacamole 1.6 upgrade:
- The previously bundled 1.4.0 client was behind what the upstream
  CAPEv2 installer deploys; this aligns the bundled JS with 1.6.0.
- Add guacamole-1.6.0-all.min.js static asset.
- Update guac/index.html and analysis/overview/_playback.html to reference
  the 1.6.0 build.
- Refactor guac-main.js from a procedural function to an ES6 class
  (GuacSession). Extracts keyboard, mouse, clipboard, scaling and error
  handling into dedicated setup methods. Differentiates error codes 514,
  515, and 522 for cleaner user-facing messages. Uses sendMouseState with
  the Guacamole 1.6 fromServer flag.
@josh-feather
Copy link
Copy Markdown
Contributor Author

@wmetcalf this will probably interest you.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements idle timeout management for Guacamole interactive analysis sessions, allowing for automatic task termination after a period of inactivity. The changes include a SessionTimeoutManager for tracking idle time and signaling completion to guest agents, backend utilities for activity detection, and a refactored frontend client updated to Guacamole 1.6.0. Review feedback correctly identifies opportunities to improve user auditing by utilizing session scope data and to fix a likely jQuery UI initialization error in the error dialog implementation.

Comment thread web/guac/consumers.py
Comment on lines +232 to +237
self.timeout_manager = SessionTimeoutManager(
vm_ip=vm_ip,
user="unknown_user",
session_id=self.guac_token,
task_id=str(self.guac_task_id),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The user parameter is hardcoded to "unknown_user". Since this consumer is typically used within an authenticated context in CAPEv2, it would be better to retrieve the actual username from the Channels scope for improved logging and auditing.

Suggested change
self.timeout_manager = SessionTimeoutManager(
vm_ip=vm_ip,
user="unknown_user",
session_id=self.guac_token,
task_id=str(self.guac_task_id),
)
user = getattr(self.scope.get("user"), "username", "unknown_user")
self.timeout_manager = SessionTimeoutManager(
vm_ip=vm_ip,
user=user,
session_id=self.guac_token,
task_id=str(self.guac_task_id),
)

Comment on lines +160 to +161
dialog.dialog({ dialogClass: 'no-close' });
dialog.dialog(this.dialogContainer);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The call dialog.dialog(this.dialogContainer) is likely incorrect for standard jQuery UI. The dialog() method expects an options object or a string command (like "open"). If the intent is to specify the container where the dialog is appended, you should use the appendTo option during initialization.

        dialog.dialog({ 
            dialogClass: 'no-close',
            appendTo: this.dialogContainer
        });

@josh-feather
Copy link
Copy Markdown
Contributor Author

@wmetcalf 1230fa0 removes the font-awesome images in the dialog title. I'll add them back in via another PR.

@kevoreilly kevoreilly merged commit 6767653 into kevoreilly:master May 15, 2026
4 checks passed
@josh-feather
Copy link
Copy Markdown
Contributor Author

@wmetcalf 1230fa0 removes the font-awesome images in the dialog title. I'll add them back in via another PR.

Fixed in #3032

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants