Sysible Controller 3.0.1
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_defaultconcatenated the raw menu entry into an echo, so an entry like0'; <cmd>; echo 'ran<cmd>as root. The value is nowshlex-quoted viaprintfand never enters the shell string. - SSH argument injection → controller root code execution (fixed). A stored SSH host's
user/ipflowed into thesshcommand 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). Hostuser/username/ip/nameare 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/fstabentry); and an agent-reportedipis 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/mefails closed toauditoron 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=1to opt into the system trust store). - Secrets are created
0600atomically (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-Secretheader.
Enrollment & host identity
host_idis 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
urlopenre-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 withsudo 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_agentgained the same purge, plus--ip/--namematching, 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 realdnf/apt/zypperupgrade 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-remotefor the update-check, stale-lock cleanup, and auto-repair of a corrupted ref store instead of dead-ending on "reference already exists";/update-statusbounds 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-runtimer 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 onGET /agentsis gone.
Platform & compatibility
- openSUSE / SLES fixes across the host command builders — sudo policy detects the
wheelgroup (nosudogroup on SUSE/RHEL, so the policy was silently ineffective); kernel listing, sshd, and firewalld paths/deps corrected; installer seedspython3-gobjectwhere 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 theunenroll_agentforce-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.