Skip to content

Sysible Controller 3.0.1

Choose a tag to compare

@sysiblesoftware sysiblesoftware released this 10 Jul 02:55
· 114 commits to main since this release
15519ad

Sysible Controller — Community 3.0.1
Released 2026-07-10

A large security-hardening and reliability release on top of 3.0.0, plus new fleet-management conveniences. Every managed-host command path, the agent↔controller protocol, and the web console were re-audited from scratch; the highlights below group the results by theme. No breaking changes — upgrade in place with sysible_controller update.

Security

Command execution & injection

  • Grub command injection → root on the managed host (fixed). cmd_set_grub_default concatenated the raw menu entry into an echo, so an entry like 0'; <cmd>; echo ' ran <cmd> as root. The value is now shlex-quoted via printf and never enters the shell string.
  • SSH argument injection → controller root code execution (fixed). A stored SSH host's user/ip flowed into the ssh command line; a value like -oProxyCommand=… was parsed by ssh as an option and ran on the controller as root (and could be triggered automatically by the read-only fleet sweeps). Host user/username/ip/name are now charset-validated at ingest and the ssh argv carries an explicit -- before the destination.
  • Defense-in-depth on the unit/mount builders. The systemd service/timer builders reject newline breakout of the unit-file heredoc and //.. in a unit name; the mount builder rejects newlines in a mount point (no injected /etc/fstab entry); and an agent-reported ip is charset-validated at ingest.

Authentication, sessions & authorization

  • Forced first-login password change is enforced server-side — every write/dispatch route (and the interactive terminal WebSocket) returns 403 until the temporary password is rotated, and a username-only credential change no longer lifts the gate. Previously this was only a frontend modal an operator could skip.
  • The console session is revalidated against the live account. A demoted or removed admin's signed cookie used to keep BFF-gated powers until the 12h token expiry. Each request now re-checks the admin token against the controller (new GET /admin/whoami, TTL-cached to one call/minute/session) and drops the session the moment the token is revoked (SYSIBLE_SESSION_REVALIDATE_TTL).
  • Admin-login throttle is per-username, and login is constant-time. The lockout was keyed on the caller's IP — which for every console login is the single BFF, so ten failures locked out all admins; it's now per-account. A decoy PBKDF2 verify runs when the username doesn't exist (admin and portal logins), closing a username-enumeration timing oracle.
  • Pure-SSH file upload/download now requires superuser on the controller (was API-key-only), matching the BFF's separation of duties for the SFTP-as-root path.
  • Logout revokes the controller token, not just the stateless cookie, so a captured token can't linger to expiry. /api/me fails closed to auditor on a missing role.

Secrets, keys & TLS

  • TLS pinning is fail-closed. A missing pin file on an https:// controller used to silently fall back to system-CA verification (any CA in the store could MITM the agent/BFF→controller channel); it now refuses to connect (SYSIBLE_ALLOW_SYSTEM_CA=1 to opt into the system trust store).
  • Secrets are created 0600 atomically (O_EXCL|O_NOFOLLOW) — the sudo-store key, the admin API key, and the cookie-signing secret — closing a world-readable umask race a local user could exploit to read the key or forge admin cookies.
  • The install-time default admin password is passed via the environment, not argv, so it's no longer visible to any local user through ps//proc/<pid>/cmdline.
  • The agent no longer puts its secret in a URL. The interactive-terminal long-poll sent the agent secret as a query parameter, recording a live credential in the controller access log every ~25s; it now uses the X-Agent-Secret header.

Enrollment & host identity

  • host_id is charset-validated at enrollment (alphanumeric plus ._-, reserved sentinels refused) — an injection-shaped or *-style id is rejected outright.
  • Enrollment adoption can no longer bypass revocation or hijack a host. The re-enroll "supersede" path matched on the unauthenticated request-body IP and could resurrect an admin-revoked host or seize an offline host's identity. Adoption is now narrow (same hostname and IP, never a revoked record, never a live host), and a revoked host must be explicitly Restored from the console.
  • Alert webhook delivery is pinned to the verified IP — the SSRF guard checked the resolved host was public but urlopen re-resolved it (DNS rebinding); the request now connects to the exact IP that passed the check (TLS cert/SNI still validated against the hostname).

New features & capabilities

  • The controller enrolls itself as a managed host. On first start it installs a privileged local agent pointed at loopback and enrolls itself, so it appears in the fleet like any other box — patch it, run scripts on it, open a terminal into it. Idempotent, best-effort, opt-out via SYSIBLE_NO_SELF_ENROLL=1; run on demand with sudo sysible_controller self-enroll.
  • Restore a revoked host in place — a Restore button (and POST /agents/{id}/restore) un-revokes without a destructive re-enroll, keeping the agent secret so a still-installed agent resumes immediately.
  • Force Delete now permanently removes a zombie host by also purging the enrollment token bound to it, so a still-running agent can't re-enroll onto the same id with the old token. tools/unenroll_agent gained the same purge, plus --ip/--name matching, DB auto-detection, and --dry-run.
  • Deployment guard. The controller reports which directory and git commit the live process is running from (GET /version, Settings → Controller Configuration), and shows a red "restart needed" banner when the on-disk code has moved since start.
  • On-demand certificate management — a "Regenerate self-signed certificate" action reissues for the current address and restarts atomically; a "Download / Regenerate agent bundle" button re-mints a fresh bundle (new single-use token) after a hostname/IP change.
  • URL-addressable navigation — every console view has a URL (?view=<key>), so you can ⌘/Ctrl-click, middle-click, open in a new tab, and bookmark/share a view.
  • Run a script across the hosts you choose — clarified in Sysible Connect and added as a "Run a script" panel in Quick System Actions.
  • Standalone New Environment button, and an offline documentation download (GET /api/docs/download).

Reliability & fixes

  • Host updates no longer fail on far-behind hosts — the agent command cap moved 5→30 min (SYSIBLE_AGENT_CMD_TIMEOUT) so a real dnf/apt/zypper upgrade isn't SIGKILLed mid-transaction; "Refresh metadata & rescan" allows 180s for a slow mirror; zypper security updates no longer skip license-gated patches.
  • Dashboard host counts are consistent and stable — enrolled/online/offline come from the instant agent inventory instead of the slow probe sweep, so a host no longer "falls off" to 0 during a sweep; revoked hosts are excluded from counts, donut, and fleet-action set.
  • Quarantined hosts fail fast instead of hanging (install reports the reason immediately; sweeps skip them).
  • Re-enrolling a host no longer auto-quarantines it or creates a duplicate "zombie" record at the same IP.
  • "Set Environment" no longer times out — hot enroll/disenroll DB writes release the SQLite WAL writer even on error, and the console write timeout was raised above the DB busy-timeout.
  • Self-update is robust — read-only git ls-remote for the update-check, stale-lock cleanup, and auto-repair of a corrupted ref store instead of dead-ending on "reference already exists"; /update-status bounds its remote check (no phantom agent); "Update agents" excludes revoked hosts.
  • Regenerating the self-signed cert no longer leaves the controller down (restart scheduled via a detached systemd-run timer outside the service cgroup); changing the controller address only flags the cert stale rather than breaking pinned clients.
  • Terminal to an offline agent fails fast; abandoned agent-terminal sessions no longer leak memory. "Upload & install a local package" works on agent-managed hosts. Username-only self-service change works. Host Enrollment refreshes are lighter (lazy portal data).
  • Web console display fixes from a full click-path audit: Webserver Portal timestamps, Environmental Policies "Save" actually persisting, Performance chart edge buckets, Lock/Unlock/Delete confirmations, self-disenroll serialization.

Performance

  • Posture / fleet-health sweeps are much faster — integrity find / walks capped at 8s (SYSIBLE_POSTURE_FIND_TMO), up to 32-way concurrency (SYSIBLE_SWEEP_CONCURRENCY); the ~5s reverse-DNS host-list stall on GET /agents is gone.

Platform & compatibility

  • openSUSE / SLES fixes across the host command builders — sudo policy detects the wheel group (no sudo group on SUSE/RHEL, so the policy was silently ineffective); kernel listing, sshd, and firewalld paths/deps corrected; installer seeds python3-gobject where needed.
  • TLS material is written atomically (cert/key/trust) so an interrupted install can't leave a half-written trust bundle.

Testing

  • New regression coverage: SSH host-injection rejection, the self-enroll bundle, /admin/whoami + per-username throttle, and the unenroll_agent force-removal path.

Want me to draft this as a GitHub Release via the API if you can grant the session tag/release permission, or is creating it from the UI/your machine easier? Either way, the commit and notes are ready on main at 15519ad.