Skip to content

fran-olivares/usulnet

v26.2.2 Security

This release includes 4 security fixes for security teams reviewing exposed deployments.

✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →
This release patches 4 known CVEs

Topics

agplv3 docker docker-deployment docker-management docker-management-tool docker-manager
+12 more
docker-swarm docker-ui go goland moby postgresql self-hosted swarm templ ui usulnet webui

Affected surfaces

auth

Summary

AI summary

Guacamole‑based full RDP web sessions are added with WebSocket proxy and defaults enabled.

Full changelog

Changelog

usulnet v26.2.2 — Beta — 2026-02-13

Added

  • RDP Web Sessions: Full web-based RDP client via Apache Guacamole daemon (guacd), with WebSocket proxy in Go backend, guacamole-common-js canvas rendering, fullscreen mode, Ctrl+Alt+Del injection, clipboard sync, and auto-resize
  • Configurable Docker Socket Path: New docker.socket config option, USULNET_DOCKER_SOCKET / DOCKER_SOCKET env vars, and automatic socket detection for rootless Docker setups. When not explicitly configured, usulnet auto-detects the socket by checking DOCKER_HOST, /var/run/docker.sock, $XDG_RUNTIME_DIR/docker.sock, /run/user/<UID>/docker.sock, and docker context inspect — eliminating the hard-coded /var/run/docker.sock dependency that broke rootless Docker deployments
  • guacd Core Service: Apache Guacamole daemon now ships as a default service alongside PostgreSQL, Redis, and NATS — no --profile rdp flag required

Fixed

  • RDP Session Drops After ~7 Seconds (close 1005): Two causes — (1) The guacd→WebSocket relay forwarded raw TCP chunks that could end mid-instruction; the Guacamole JS parser calls close("Incomplete instruction.") on any partial data, killing the tunnel immediately. Fixed by buffering reads and only sending complete instructions (up to the last ; boundary). (2) The Guacamole JS tunnel sends internal ping instructions (0.,4.ping,…) every 500ms to keep its 15-second receive-timeout alive; these were forwarded to guacd (which ignores them) and never echoed back. Fixed by intercepting empty-opcode messages and echoing them to the browser
  • RDP Canvas Hidden Behind Background in Non-Fullscreen Mode: The #rdp-display container used display: block in windowed mode but display: flex only in fullscreen, causing the Guacamole canvas element to be obscured by the parent's background. Fixed by applying display: flex; align-items: center; justify-content: center in both modes, adding z-index: 1 to .guac-display, and using x-cloak on overlays to prevent a flash of loading/error UI before Alpine.js initializes
  • Node Detail Page Missing Docker Engine Fields: Version, Storage Driver, Logging Driver, Cgroup Driver, Default Runtime, OS Type, and Architecture showed "—" because these fields were never mapped through the chain: Docker SDK system.Infodocker.DockerInfomodels.HostDockerInfoDockerInfoView → template. Added field mapping across all four layers and fallback to live Docker info when database values are empty
  • False Update Alerts for latest-Tagged Containers: Registry clients (Docker Hub, GHCR, Generic) now return the digest of the latest tag itself instead of resolving it to the highest semver tag, eliminating false positives where "latest" vs "1.27.0" always triggered an update notification
  • Container Digest Not Populated in Batch Checks: ContainerInfo.Digest now sourced from Docker's ImageID field, enabling accurate digest-based comparison for latest-tagged images
  • CSRF Validation on Git Integration Forms: The Gitea list and repo detail handlers used ctx.Value("csrf_token") with a plain string key instead of the typed ContextKey, causing the CSRF token to always be empty; replaced with h.getCSRFToken(r)
  • Container Bulk Action Form Missing CSRF Token: Added csrf_token hidden input to the bulk-action form for defense in depth (was relying solely on HTMX global header injection)
  • Guacamole JS "module is not defined" in Browser: The bundled guacamole-common.min.js was the CJS (Node.js) build ending with module.exports=Guacamole; — browsers have no module global, causing ReferenceError. Stripped the CJS export since the file already sets Guacamole as a global via IIFE
  • RDP WebSocket Rejected by Browser (missing subprotocol): guacamole-common-js opens WebSocket with "guacamole" subprotocol but the Go upgrader didn't negotiate it, causing the browser to drop the connection immediately. Added dedicated rdpUpgrader with Subprotocols: ["guacamole"]
  • RDP Session: guacd Response Not Forwarded to Browser: After completing the Guacamole handshake (select→args→connect), the Go handler started the relay loop but never forwarded guacd's initial ready response to the JS client, so the client never transitioned to CONNECTED state
  • RDP Session Dies After ~5 Seconds: Two causes — (1) Missing VERSION_1_5_0 protocol version parameter caused guacd to fall back to a legacy protocol incompatible with the 1.5.0 JS client; (2) http.Server.WriteTimeout deadline was inherited by the hijacked WebSocket net.Conn, killing the connection after the server's 30s write timeout. Fixed by adding the version parameter to the handshake and clearing deadlines with SetDeadline(time.Time{}) after upgrade
  • Monaco Editor "Save & Commit" CSRF Failure: The doCommit() fetch POST in the Monaco editor did not include a CSRF token, causing the server to return a 403 plain-text response which the JS then failed to parse as JSON (Unexpected token 'C'). Added CSRF token from <meta name="csrf-token"> to both the X-CSRF-Token header and csrf_token form field, and improved error handling to check resp.ok before calling resp.json()
  • Backup Creation Fails with NOT NULL Constraint: Migration 006 defined name VARCHAR(255) NOT NULL and target_type VARCHAR(50) NOT NULL on the backups table, but the Go model and repository layer use target_name and type columns instead — the legacy columns were never populated, causing every INSERT to fail. Added migration 030 to drop orphaned columns (name, target_type, storage_path, storage_type) from backups and (name, target_type, storage_type, retention_count) from backup_schedules. Also fixed the backup creation form to send target_name (human-readable name) instead of only the raw container/volume/stack ID
  • getUserID() Always Returned Nil: The helper used r.Context().Value("user_id") with a plain string key, but the auth middleware stores the user under the typed ContextKey("user") as *UserContext; replaced with GetUserFromContext() — fixes Git connection creation, GitOps sync, and enterprise handlers all receiving zero UUID for created_by
  • unknown provider type: Gitea: NewProvider() factory matched provider types case-sensitively; any casing variation (e.g. "Gitea" vs "gitea") fell through to the error default — now normalizes to lowercase before matching
  • Guacamole JS Not Copied into Docker Image: Dockerfile only copied style.css and favicon.ico into the runtime stage; web/static/js/ (containing guacamole-common.min.js) was never included, causing a 404 in production

Changed

  • HOST_TERMINAL_ENABLED defaults to true: Host terminal and host files browser are now enabled by default without requiring manual environment variable configuration
  • GUACD_ENABLED defaults to true: RDP web access is enabled by default across all deployment modes (docker-compose.yml, docker-compose.dev.yml, docker-compose.prod.yml)
  • NeedsUpdate() Logic: Handles latest=latest with no digests as "no update needed" instead of always reporting an update
  • Guacamole JS Bundled Locally: [email protected] now served from /static/js/guacamole-common.min.js instead of an external CDN, eliminating external dependency for RDP web sessions

Security

  • CSRF token extraction in Git integration handlers now uses the proper typed context key, preventing token bypass on connection creation, sync, test, and delete operations
  • getUserID() now uses the typed ContextKey via GetUserFromContext(), preventing user identity bypass that caused created_by foreign key violations
  • Monaco editor doCommit() and saveSnippet() now include CSRF tokens, preventing form submission bypass from the code editor
  • Database migration 030 removes legacy NOT NULL columns that were never populated, eliminating a denial-of-service vector on backup creation

Full Changelog: https://github.com/fr4nsys/usulnet/compare/v26.2.1...v26.2.2

Full Changelog: https://github.com/fr4nsys/usulnet/compare/v26.2.1...v26.2.2

Security Fixes

  • CSRF token extraction fixed in Git integration handlers – prevents bypass on connection creation/sync/delete.
  • getUserID() now uses typed context key – prevents user identity bypass causing foreign‑key violations.
  • Monaco editor commit/save actions now include CSRF tokens – blocks form submission bypass.
  • Migration 030 removes unused NOT NULL columns, eliminating a denial‑of‑service vector on backup creation.

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track fran-olivares/usulnet

Get notified when new releases ship.

Sign up free

About fran-olivares/usulnet

All releases →

Related context

Beta — feedback welcome: [email protected]