Skip to content

Auto-patch (opt-in per host)

Auto-patch lets monsys handle CVEs autonomously on hosts that explicitly opt in. Without this flag monsys never patches automatically — that’s the default and how the rest of the platform behaves.

  • Hosts where a minute of downtime is acceptable (install → optional restart)
  • Hosts where you trust distro-maintainer QA (Ubuntu/Debian/RHEL security updates)
  • Fleet layers you no longer want to patch manually (dev boxes, staging, CI runners)

Don’t enable on:

  • Production DB primaries (patch after canary)
  • Hosts where an app restart is a customer-facing hit (use the EAT flow with a maintenance window)
  • Kernel updates (deliberately NOT auto-patchable — reboot risk)
CategorySourceEAT actionRestart?
OS packagesapt / dnf / apk / zypperinstall_package per (package, version)handled by the package manager
Application depsnpm / pip / composer / RubyGems / Goupdate_packages per (project, ecosystem)install-only in v1 — no automatic service restart
KernelNOT auto-patchable in v1

Filtering:

  • fixed_version IS NOT NULL — only CVEs with a known fix
  • Severity ≥ configured threshold (CRITICAL or CRITICAL+HIGH)
  • acknowledged_at IS NULL — CVEs the operator previously dismissed are skipped
  • Max 25 packages per host per cycle — sanity cap against runaway
  • Warmup: 2 minutes after hub start
  • Interval: 30 minutes
  • Per-host rate limit: at least 25 minutes since the previous tick
  • Hosts with no open CVEs get touched too — last_run_at update prevents re-polling

Governance tab on agent detail → section Auto-patching (opt-in):

  1. Tick OS packages and/or Application dependencies
  2. Choose the minimum severity: CRITICAL or CRITICAL + HIGH
  3. Click Save (TOTP) — a dialog asks for your TOTP code
  4. Confirmation + timestamp appears below the section

Disabling doesn’t need TOTP — flipping the flag back to false is by definition a safe fallback.

Every phase of auto-patch produces an entry in the attestation chain.

WhenEntry kindPayload
Flag flipped on hostremediation.auto_flag_setos_packages, app_deps, min_severity, set_by, turned_on
Worker dispatches EATremediation.auto_proposedcategory, package, target_version, cve_id, severity, eat_nonce, flag_set_by, flag_set_at
Agent reports successremediation.auto_appliedeat_nonce, exit_code, note
Agent reports rollbackremediation.auto_rolled_backsame, with exit_code ≠ 0 or rollback note

An external auditor walks from auto_appliedauto_proposed (via eat_nonce) → flag_set_byauto_flag_set (via subject_ref = agent_id). Every autonomous patch is cryptographically linked to the user who gave consent.

Every cycle always fires an alert, even on success:

  • infoAuto-patch dispatched: <pkg> → <version> on every EAT issued
  • warningAuto-patch rolled back on restart-fail or non-zero exit

That way you see in the standard alerts view which hosts run autonomously, even if you never look at the attestation chain.

The agent-side wrappers (monsys-action install for OS packages, monsys-package-update for app deps) do:

  1. Snapshot — lockfile + node_modules (or dpkg status for apt) is preserved
  2. Install — the requested upgrade
  3. Restart (if restart_service is set) — not present in v1 auto-patch
  4. Rollback — if a restart fails, roll back to the snapshot

If the install itself fails (dpkg config conflict, npm ERESOLVE): no rollback needed because nothing changed. Non-zero exit code comes back via PostEmergencyResult and emits auto_rolled_back.

Kernel updates need a reboot to take effect. Reboots have side effects the wrapper can’t roll back (services that don’t autostart, filesystem mounts, network config). The risk doesn’t outweigh the convenience — kernel CVEs are urgent enough that an operator can do a manual TOTP through the kernel-CVE pipeline with canary + verify.

npm install swaps files on disk, but the running process has already loaded the old code. A service restart is needed to make the fix effective. In v1 there’s no automatic mapping from (project_path, ecosystem) → systemd unit, so we don’t force a restart that could be wrong. The patch is staged; the operator restarts when convenient. A future restart_service field per project can make that linkage explicit.

Compared to Root/Aikido and OS auto-updates

Section titled “Compared to Root/Aikido and OS auto-updates”
  • Root (Aikido) does hardened container images — dependency-level, drop-in replacement. Complementary, not competitive. If you run containers: Root for the images, monsys for the host.
  • Ubuntu unattended-upgrades / dnf-automatic patch OS packages too, but without attestation, without centralized policy, without a rollback-alert loop. Monsys auto-patch is useful on distros where unattended-upgrades gives too little context for NIS2/CRA audit.

agents has (mig 113):

auto_patch_os_packages BOOLEAN NOT NULL DEFAULT FALSE
auto_patch_app_deps BOOLEAN NOT NULL DEFAULT FALSE
auto_patch_min_severity TEXT NOT NULL DEFAULT 'HIGH'
CHECK IN ('CRITICAL', 'HIGH')
auto_patch_enabled_by UUID REFERENCES users(id)
auto_patch_enabled_at TIMESTAMPTZ
auto_patch_last_run_at TIMESTAMPTZ

The partial index idx_agents_auto_patch_enabled keeps the worker query O(log n) on the enabled set regardless of total fleet size.

  • GET /api/v1/agents/:id/auto-patch — read (viewer role)
  • PATCH /api/v1/agents/:id/auto-patch — write (admin role + TOTP purpose agent_auto_patch)

Body:

{
"os_packages": true,
"app_deps": false,
"min_severity": "HIGH"
}

All three fields are optional; only present ones are updated. enabled_by + enabled_at are only updated when you turn a flag on — turning off leaves those audit fields so the last consent actor stays traceable.