Neutron, our AI engine, scored 97.1% on UC Berkeley's CyberGym benchmark. Learn more

Deep Dive

Deep Dive

Checkmk RCE: Three Critical Flaws in One Patch (CVE-2026-121374)

Three Checkmk flaws let rule editors, malicious remote sites or compromised hosts run code as the site user; fixed in 2.5.0p19.

TL;DR

  • Checkmk 2.5.0p19 and 2.4.0p40 fix three critical flaws that each end in code execution as the site user. The vendor published both patch releases on November 11, 2026.
  • Every site before 2.5.0p19 on the 2.5 line, or before 2.4.0p40 on the 2.4 line, is affected. Version 2.3.0 is end-of-life and receives no fix.
  • All three are rated CVSS v4.0 9.4 Critical. No public proof of concept and no exploitation in the wild are known, and none is in CISA KEV as of November 11, 2026.
  • Upgrade to 2.5.0p19 or 2.4.0p40. If you cannot, restrict rule editing and do not connect the central site to untrusted remote sites; see "How do I fix it?" for the limits.

Am I affected?

The version requirement applies to every flaw; the other conditions apply separately to their named CVEs:

  • You run a Checkmk site on 2.5.0 before 2.5.0p19, or on 2.4.0 before 2.4.0p40. Version 2.3.0 is affected and end-of-life.
  • For CVE-2026-121374, at least one user may edit rules in at least one folder. By default this includes the "Normal monitoring user" role when it has permissions on a folder.
  • For CVE-2026-118965, your central site synchronises discovered host labels from remote sites you do not fully trust.
  • For CVE-2026-120495, you run the Pro, Ultimate, Cloud or Ultimate MT edition with Robotmk synthetic monitoring, and a monitored host can send crafted agent output.

The first flaw does not require Kubernetes. A user who can create a rule can add a Kubernetes special agent rule even when the site monitors no cluster. The third flaw needs automatic service discovery for the host, or a monitoring administrator who confirms the Robotmk services by hand.

Branch Affected versions Fixed version
2.5 2.5.0 to 2.5.0p18 2.5.0p19, released 2026-11-11
2.4 2.4.0 to 2.4.0p39 2.4.0p40, released 2026-11-11
2.3 all 2.3.0 releases None; end-of-life
3.0 3.0.0b1 Not yet released; carries the fixes

Check the version on every site, including remote sites:

omd version

Inside one site, the monitoring core reports the same version:

sudo -u <site> cmk --version

The web interface shows it under Help > About. omd sites lists the site names when you are not sure which ones exist.

How do I fix it?

Install the 2.5.0p19 or 2.4.0p40 package for your edition and distribution, then update each site. The vendor's werk pages list the release as compatible, with no manual interaction after the update.

  1. Install the matching package for your edition, for example check-mk-raw-2.5.0p19, check-mk-enterprise-2.5.0p19 or the equivalent 2.4.0p40 package.
  2. Update every site, including remote sites: omd update <site>
  3. On a Checkmk appliance, upload the 2.5.0p19 or 2.4.0p40 image in the appliance interface and run the update there.
  4. Confirm the result: omd version
  5. Repeat on each distributed site. The host-label flaw is fixed on the central site; the Kubernetes and Robotmk flaws are fixed on the site that runs those checks.

If you cannot update immediately, reduce exposure with these measures. Each one limits an attack path, and none replaces the patch.

  1. Remove the "Edit rules" permission from every non-administrative role, and review which folders those roles can reach. An administrator can still exploit CVE-2026-121374, so this only removes the default path for ordinary monitoring users.
  2. Do not connect a central site to remote sites you do not control. CVE-2026-118965 has no configuration switch; the fix is the update.
  3. Treat monitored hosts as untrusted for CVE-2026-120495. Restrict who can add services and turn off automatic service discovery for hosts you do not administer. A host that can send agent output can still reach the flaw when an administrator adds its services.
  4. Move any 2.3.0 site to 2.4.0p40 or 2.5.0p19. The 2.3.0 line is end-of-life and has no fixed release.

What is the root cause?

The three flaws share one pattern. A value controlled by a lower-privileged party reaches a command line or a file path without confinement. The site user's privileges then turn the write into code execution. Each entry point is different, so each fix is different.

The first flaw is in the Kubernetes special agent. The rule option "Cluster resource aggregation > Exclude nodes based on their role" collects role names. The server-side call passes those names to the agent as separate command-line arguments after one option:

match params.cluster_resource_aggregation:
    case ("cluster_aggregation_exclude_node_roles", list(excluded_roles)):
        args.append("--cluster-aggregation-exclude-node-roles")
        args.extend(excluded_roles)

The agent parses its arguments with argparse. The vulnerable parser declared the option with nargs="+", so it consumed every following token that did not look like an option:

group.add_argument(
    "--cluster-aggregation-exclude-node-roles",
    nargs="+",
    default=["control-plane", "infra"],
    dest="roles",
)

A role value that begins with --profile is not a value to argparse. It is a new option, and the next role value becomes its argument. The --profile option writes profiling data to a file and, in the same directory, an executable Python launcher whose source contains the profile path:

f'stats = pstats.Stats("{self._profile_file}")\n'

A path containing a double quote and Python syntax therefore changes the launcher's source. The werk's assessment is that a rule editor can write an executable Python script with controlled content and reach code execution as the site user.

The second flaw is in the host-label synchronisation. The central site asks each remote site for the discovered host labels of its hosts, then writes the returned content to disk. The vulnerable code joined the remote-supplied file name to the label directory with no check:

def save_updated_host_label_files(updated_host_labels: list[UpdatedHostLabelsEntry]) -> None:
    for file_name, mtime, content in updated_host_labels:
        file_path = cmk.utils.paths.discovered_host_labels_dir / file_name
        store.save_text_to_file(file_path, content)
        os.utime(file_path, (mtime, mtime))

A file name containing ../ escapes the directory, and a name ending in a different suffix writes a file the site later imports or executes. A compromised remote site therefore writes arbitrary files on the central site.

The third flaw is in the Robotmk test service. The service takes a test case name from the agent data and uses it to create the folder for the HTML log. The final path was not validated, so a crafted test case name traverses out of the log directory. The log content is also user-defined, which turns the write into code execution in the vendor's assessment.

How is it exploited?

Each flaw needs a different starting position. None needs a vulnerability in the monitored host itself, except the third, which needs a host that can send agent output.

For CVE-2026-121374:

  1. A user who can edit rules creates or changes a Kubernetes special agent rule.
  2. In "Exclude nodes based on their role", the user enters --profile as one role and a path as the next value.
  3. When the special agent runs, argparse reads --profile as an option and the path as its argument.
  4. The agent writes the profiling data and the launcher script at the chosen path. The launcher contains the path inside a Python string.
  5. A path crafted with a quote and Python code changes the launcher's source. The vendor treats the resulting executable script as code execution as the site user.

For CVE-2026-118965:

  1. An attacker controls a remote site in a distributed setup, or compromises one.
  2. The remote site answers the central site's host-label request with a file name that contains ../.
  3. The central site's synchronisation writes the supplied content to the resolved path.
  4. The attacker chooses a path the site user imports or executes, which gives code execution on the central site.

For CVE-2026-120495:

  1. An attacker can add agent output for a monitored host, for example through a malicious agent plug-in or spool file. The werk says this usually needs root on the monitored host.
  2. The crafted output defines a Robotmk test case name that traverses out of the HTML log directory.
  3. The site writes an HTML log with attacker-controlled content at the resolved path.
  4. The vendor rates the result as code execution as the site user.

How does the attack flow?

Three different inputs reach the same outcome: a file written as the Checkmk site user, which the vendor treats as code execution.

flowchart LR A["Rule editor sets an
excluded node role"] --> B["Role read as a
--profile option"] C["Malicious remote site
returns a label filename"] --> D["Filename joined to
the label directory
with no path check"] E["Compromised host sends
a Robotmk test name"] --> F["Test name used as
the log folder
with no path check"] B --> G["File written as
the site user"] D --> G F --> G G --> H["Code execution as
the site user"] style D fill:#f8d7da style F fill:#f8d7da

What can an attacker do?

An attacker who reaches code execution runs commands as the Checkmk site user. That account reads the monitoring configuration, the credentials Checkmk uses to reach monitored devices, and the site's own files. The conditions differ by flaw.

  • CVE-2026-121374: the attacker needs a user who can edit rules in at least one folder. The site does not need to monitor Kubernetes, because the attacker creates the rule. The result is code execution as the site user.
  • CVE-2026-118965: the attacker needs control of a remote site in a distributed setup. The result is arbitrary file write on the central site and code execution as the central site user.
  • CVE-2026-120495: the attacker needs to supply crafted agent output for a monitored host, which the werk says usually requires root on that host, and either automatic service discovery or an administrator who adds the Robotmk services. The result is code execution as the site user.

The site user is not root on the Checkmk server. It can still read the monitoring data and the stored credentials for monitored systems, so treat a compromise as an exposure of those systems as well.

Why does the fix work?

The fixes remove the ambiguity between a value and an option, and they confine the paths the site writes.

The Kubernetes fix passes each role as its own option with an attached value, and the parser now appends each one:

     match params.cluster_resource_aggregation:
         case None:
-            args.extend(["--cluster-aggregation-exclude-node-roles", "control-plane", "infra"])
+            args.extend(
+                f"--cluster-aggregation-exclude-node-role={role}"
+                for role in ("control-plane", "infra")
+            )
         case ("cluster_aggregation_include_all_nodes", None):
             args.append("--cluster-aggregation-include-all-nodes")
         case ("cluster_aggregation_exclude_node_roles", list(excluded_roles)):
-            args.append("--cluster-aggregation-exclude-node-roles")
-            args.extend(excluded_roles)
+            args.extend(
+                f"--cluster-aggregation-exclude-node-role={role}" for role in excluded_roles
+            )
     group.add_argument(
-        "--cluster-aggregation-exclude-node-roles",
-        nargs="+",
-        default=["control-plane", "infra"],
+        "--cluster-aggregation-exclude-node-role",
+        action="append",
+        default=None,
         dest="roles",
+        metavar="ROLE",
     )

A role is now part of the --cluster-aggregation-exclude-node-role=<role> token, so argparse cannot read it as a separate option. The agent also restricts --profile to ~/tmp/check_mk/debug and writes the path into the launcher as a Python literal:

+def _profile_file(filename: str) -> Path:
+    path = Path(filename).resolve()
+    allowed = (Path.home() / _PROFILE_DIR).resolve()
+    if not path.is_relative_to(allowed):
+        raise argparse.ArgumentTypeError(f"Profiles can only be stored in {allowed}")
+    return path
-                f'stats = pstats.Stats("{self._profile_file}")\n'
+                f"stats = pstats.Stats({str(self._profile_file)!r})\n"

The host-label fix resolves the target and requires it to stay under the label directory, carry the .mk suffix and use a valid host name as its stem:

+def _make_secure_path(base: Path, leaf: str) -> Path:
+    path = Path(base).resolve()
+    resolved = (path / leaf).resolve()
+    if not resolved.is_relative_to(path):
+        raise ValueError(f"Path {resolved} is not relative to {path}")
+    if resolved.suffix != ".mk":
+        raise ValueError(f"Path {resolved} does not have .mk suffix")
+    HostName(resolved.stem)  # would raise if validation fails
+    return resolved
+
+
 def save_updated_host_label_files(updated_host_labels: list[UpdatedHostLabelsEntry]) -> None:
     for file_name, mtime, content in updated_host_labels:
-        file_path = cmk.utils.paths.discovered_host_labels_dir / file_name
+        file_path = _make_secure_path(cmk.utils.paths.discovered_host_labels_dir, file_name)

The Robotmk change confines generated logs to ~/var/robotmk/html_logs and returns an UNKNOWN service state when a log would land outside it. The Robotmk change ships in the same patch release, and its werk is the public record of the new path rule.

How do I check if I was compromised?

Checkmk publishes no indicators of compromise for these flaws. An attempt that fails, or one that writes a file the site never executes, can leave no trace beyond the file itself. Review the three places the flaws touch.

  1. Check the audit log for rule changes that add a Kubernetes special agent rule or change "Exclude nodes based on their role". In the web interface, open Setup > General > Audit log and filter for the Kubernetes rule set.
  2. Look for unexpected files in the site's debug directory: sudo -u <site> find ~/tmp/check_mk/debug -type f -name '*.py' 2>/dev/null
  3. List the discovered host-label files and confirm every name is a host you monitor: sudo -u <site> ls -la ~/var/check_mk/discovered_host_labels
  4. On Pro, Ultimate, Cloud and Ultimate MT sites, look for HTML logs outside the expected directory: sudo -u <site> find ~/var/robotmk/html_logs -type f 2>/dev/null A file under a path that contains .. cannot exist as a literal name, so review files whose parent directories are not test-case folders you recognise.

A hit is a lead to investigate, not proof of exploitation. The audit log only helps if the attacker's rule change was recorded and the log was not altered afterward.

Timeline

  • 2026-10-29: the fix for CVE-2026-118965 is committed to the Checkmk repository.
  • 2026-11-02: the fix for CVE-2026-120495 is recorded in its werk.
  • 2026-11-03: the fix for CVE-2026-121374 is committed to the Checkmk repository.
  • 2026-11-11: Checkmk releases 2.5.0p19 and 2.4.0p40 and publishes the three CVE records.

The fixes landed between October 29 and November 11, 2026, and the patch release closed a 13-day window
Figure 1: From the first fix commit to the patch release. Source: Checkmk werks and release notes, checked November 11, 2026

FAQ

Does this affect the Community edition?

Yes for CVE-2026-121374 and CVE-2026-118965, which affect every edition. CVE-2026-120495 affects only the Pro, Ultimate, Cloud and Ultimate MT editions, because Robotmk synthetic monitoring is not part of the Community edition. The fixed 2.5.0p19 and 2.4.0p40 packages exist for every edition.

I do not monitor Kubernetes. Am I still affected by the first flaw?

Yes, if a user can edit rules. The attacker creates the Kubernetes special agent rule, so the site does not need to monitor a cluster. Remove the rule-editing permission from non-administrative roles until you update.

Is version 2.3.0 fixed?

No. The 2.3.0 line is end-of-life, and the three werks list it as affected without a fixed release. Move those sites to 2.4.0p40 or 2.5.0p19.

Was any of this exploited in the wild?

No exploitation is known as of November 11, 2026, and none of the three CVEs is in CISA KEV. The Kubernetes flaw was found during internal review, and the host-label flaw arrived through the vendor's security disclosure process. That can change, so check the KEV catalog before you plan a long delay.

Do I need to change my configuration after upgrading?

No. All three werks are marked compatible with no manual interaction. The Kubernetes agent accepts the new per-role option, and the host-label and Robotmk checks apply the new path rules automatically.

How do I know which edition I run?

omd version prints the version of each site, and the package name on the server names the edition, for example check-mk-raw for Community or check-mk-enterprise for Enterprise. The web interface shows the edition under Help > About.

Find exposed Checkmk servers with Ostorlab

Use Ostorlab Attack Surface Discovery to inventory the internet-facing hosts and web interfaces you own, so you know which Checkmk sites are reachable and need the update first. It maps exposed services; it does not detect this CVE.

References

  • Checkmk werk 17994, "Kubernetes: Arbitrary file write and code execution via excluded node roles", CVE-2026-121374, dated 2026-11-03: https://checkmk.com/werk/17994 (checked November 11, 2026)
  • Checkmk werk 22120, "Code execution through compromised remote site", CVE-2026-118965, dated 2026-10-29: https://checkmk.com/werk/22120 (checked November 11, 2026)
  • Checkmk werk 22624, "Code execution through Robotmk HTML log test service", CVE-2026-120495, dated 2026-11-02: https://checkmk.com/werk/22624 (checked November 11, 2026)
  • The CVE-2026-121374 record, Checkmk CNA, published 2026-11-11: https://www.cve.org/CVERecord?id=CVE-2026-121374 (checked November 11, 2026)
  • The CVE-2026-118965 record, Checkmk CNA, published 2026-11-11: https://www.cve.org/CVERecord?id=CVE-2026-118965 (checked November 11, 2026)
  • The CVE-2026-120495 record, Checkmk CNA, published 2026-11-11: https://www.cve.org/CVERecord?id=CVE-2026-120495 (checked November 11, 2026)
  • Fix commit 5a2c448efa for CVE-2026-121374, 2026-11-03: https://github.com/Checkmk/checkmk/commit/5a2c448efafb1e3f2dab75babdecb5f99d0a0618 (checked November 11, 2026)
  • Fix commit 8efcf83a15 for CVE-2026-118965, 2026-10-29: https://github.com/Checkmk/checkmk/commit/8efcf83a153b201ce640a6c1da9de9c52d9c3650 (checked November 11, 2026)
  • Fix commit 4ba4c9f638 for CVE-2026-120495, 2026-11-03: https://github.com/Checkmk/checkmk/commit/4ba4c9f638cf83509970e78468f79bf846327d7b (checked November 11, 2026)
  • Checkmk 2.5.0p19 release notes index, dated November 11, 2026: https://checkmk.com/werks?version=2.5.0p19 (checked November 11, 2026)
  • Checkmk 2.4.0p40 release notes index, dated November 11, 2026: https://checkmk.com/werks?version=2.4.0p40 (checked November 11, 2026)
  • CISA's Known Exploited Vulnerabilities catalog, catalog version 2026.11.10, which does not list the three CVEs: https://www.cisa.gov/known-exploited-vulnerabilities-catalog (checked November 11, 2026)
  • Figure 1, the fix-to-release timeline: dates from the three werks and the two release indexes listed in References, checked November 11, 2026.