Direct answer: CVE-2026-115731 lets any holder of a Puppet agentAI agentAn AI program that performs work steps by itself, for example reading a message, drafting a reply, and recording the result.Open the glossary certificate run code on the Puppet server through an agent report. CVE-2026-115732 lets 1 node pose as another node through a similar certificate name. Today, check the version, update to OpenVox 8.18.0, Puppet Core 8.22.2, or PE 2025.12.1, then restart the server.

Perforce gives CVSS 4.0 9.4 to CVE-2026-115731 and 9.0 to CVE-2026-115732. OpenVox gives 9.4 and 9.5. Both CVEs have the critical label.

Main condition: You run a Puppet, OpenVox, or Puppet Enterprise server yourself. Limit: This article has no request contents, payloads, or attack sequence.

We read the Perforce advisory Report Deserialization Code Execution, the Perforce advisory Certificate Name Authorization Bypass, the OpenVox advisory GHSA-ffr6-rwpm-p5wq, the OpenVox advisory GHSA-q4hm-33rc-572g, the CVE-2026-115731 record, the CVE-2026-115732 record, the Vox Pupuli blog of 3 November 2026, the Neodyme research, and CISA KEV on 4 November 2026, from 13.35 to 13.55 WIB. We tested 3 check scripts in podman containers with the official openvoxserver:8.17.0 (vulnerable) and 8.18.0 (safe) images, and with official OpenVox packages on Ubuntu 24.04. We did not test Puppet Enterprise, Puppet Core, or an attack.

The problem: 1 Puppet server holds the keys to all your servers

The Puppet server decides the contents of every managed server, so a flaw in the Puppet server is a flaw in all of them.

Puppet is a configuration management tool. DevOps teams use it to install packages, users, and configuration on tens to thousands of servers from 1 place. OpenVox is the community fork of Puppet that Vox Pupuli maintains.

Every managed server runs an agent. The agent holds a certificate from the CA on the Puppet server, then asks for a catalog and sends a report. Catalogs often hold database passwords, APIAPIThe official door 2 systems use to exchange data, without anybody copying it by hand.Open the glossary tokens, and other keys.

On 3 November 2026, Perforce and OpenVox released patches together for 2 flaws in the Puppet server. Neodyme, the team that found the flaws, published its research on the same day.

Timeline: OpenVox 8.17.0 on 7 October, openvoxdb advisory 21 October, 29 October releases still vulnerable, patches and advisories 3 November, not on KEV 4 November.
Grey dots: releases and research. Teal dot: the releases that close the flaws. Red dot: advisory and CVE publication. Amber dots: status and the image advisory. Source: OpenVox GitHub releases, Perforce and OpenVox advisories, the Neodyme blog, and CISA KEV.

The OpenVox releases of 29 October do not close these flaws. So a server that you updated last week is still vulnerable.

How it works: agent reports and certificate names

The 2 flaws are in 2 paths that every agent uses every 30 minutes: send a report and check access.

This section uses the official advisory summaries. Its purpose is to help you choose the right steps, not to explain an attack.

Summary of CVE-2026-115731 (agent report)

According to the CVE-2026-115731 record, the report-processing code in Puppet Server reads report data without a safe type limit. A client with an agent certificate that the CA signed can run code on the server as the Puppet service user.

The CVE record also gives an important condition: when the CA autosigns without strong verification, an attacker with no account can get that certificate. The Neodyme research says that this access can expose the CA key and the catalogs of all nodes.

Summary of CVE-2026-115732 (certificate name)

According to the CVE-2026-115732 record, the certificate name check in the trapperkeeper-authorization component accepts a name that is longer or shorter than the allowed host name. Example from the OpenVox advisory GHSA-q4hm-33rc-572g: a certificate for a name that extends the name of a node passes as that node.

As a result, 1 node can read the catalog of another node, including the secrets in it, and send false facts. For Puppet Enterprise, Perforce says that this flaw also opens CA administration and remote commands on nodes.

Scores of the 2 CVEs and 1 image advisory

The scores differ per source, so this table shows both. The OpenVox advisory scores CVE-2026-115732 with no login condition, so its score is higher.

IDAreaCVSS 4.0 PerforceCVSS 4.0 OpenVoxAccessSafe in
CVE-2026-115731Agent report: report data runs code on the Puppet server9.49.4A valid agent certificateOpenVox 8.18.0, 9.2.0, Puppet Core 8.22.2, 9.1.3
CVE-2026-115732Certificate name: 1 node is allowed as another node9.09.5A certificate with a similar nameThe same releases
GHSA-c4wc-g5pp-7wfpopenvoxdb 8.15.0 image: ALLOWLIST not applied-9.9Network access to OpenVoxDBImage 8.17.0, Vox Pupuli advises 8.18.0
Three cards: CVE-2026-115731 agent report score 9.4, CVE-2026-115732 certificate name score 9.0 and 9.5, openvoxdb image score 9.9. Safe: OpenVox 8.18.0, Puppet Core 8.22.2.
(1) Red cards: the 2 CVEs in the Puppet server. (2) Amber card: the openvoxdb image flaw. (3) Amber panel: the flaw condition. (4) Teal box: the safe versions. Source: Perforce advisories, OpenVox GitHub advisories, CVE records, and Rama Digital test.

GHSA-c4wc-g5pp-7wfp came out on 21 October 2026 for the container-openvoxdb 8.15.0 image. It is not one of the 2 CVEs above, but the Vox Pupuli blog of 3 November 2026 lists it in the same list.

Exploitation status on 4 November 2026

We found no attack reports and no CISA KEV entry for the 2 CVEs. But the OpenVox advisory GHSA-ffr6-rwpm-p5wq already has a proof of concept. Rama Digital analysis: treat the attack code as public, and update today.

Who has the highest risk

The risk depends on who can get a certificate and what your catalogs hold, not only on the version.

In our test, the official OpenVox 8.17.0 and 8.18.0 container images use AUTOSIGN=true by default. So a server in a container with the default value signs every certificate request.

Puppet server conditionRiskReason
Autosign true or an autosign list with a wildcardHighestAnyone who reaches port 8140 can get a certificate
Port 8140 open to the internetHighAn outside attacker can request a certificate and call the API
Catalogs or Hiera hold passwords and tokensHighA node that poses as another node can read those secrets
Hundreds of nodes, short-lived VMs, or autoscalingCheckEvery node has a valid certificate. 1 broken node is enough for CVE-2026-115731
Open source Puppet or OpenVox 7No patchNo fix release exists. Plan a line move
Safe version, autosign off, port 8140 limitedLowStill look for signs from the time before the update
Six risk cards: autosign on, port 8140 open to the internet, and catalogs with passwords are red; many nodes and versions with no patch are amber; safe is grey.
Red cards: the highest risk. Amber cards: check or plan. Grey card: low risk. Bottom box: the exploitation status on 4 November 2026. Source: Perforce and OpenVox advisories, CISA KEV, and Rama Digital analysis.

Puppet servers often run on VMs that also need kernel patches, for example for the Linux kernel TCP flaw CVE-2026-98432. Teams that use Rancher for Kubernetes should also read the Rancher CVE-2026-94290 warning.

Vulnerable and safe versions

The vulnerable code is in 2 packages on the server machine: the server package and the agent package. The server loads Ruby code from the agent package, so update both.

ProductVulnerable versionsStatus on 4 November 2026Action
OpenVox Server8.17.1 and below, 9.0.0 to 9.1.1VulnerableUpdate to 8.18.0 or 9.2.0
OpenVox Agent on the server machine8.31.2 and below, 9.0.0 to 9.2.1VulnerableUpdate to 8.32.0 or 9.3.0
Puppet Core8.11.0 to 8.22.1, 9.0.0 to 9.1.2VulnerableUpdate to 8.22.2 or 9.1.3
Puppet Enterprise2023.8.0 to 2023.8.12, 2025.0.0 to 2025.12.0, 2026.0.0VulnerableUpdate to 2023.8.13, 2025.12.1, or 2026.0.1
Open source Puppet8.10.0 and below, 7.x, 6.xNo patchMove to OpenVox 8 or Puppet Core
OpenVox 77.xNo patchMove to OpenVox 8.18.0
A table of 6 products: OpenVox Server, OpenVox Agent, Puppet Core, and Puppet Enterprise are vulnerable and have a patch. Open source Puppet and OpenVox 7 have no patch.
Red: vulnerable or no patch. Amber: no patch, move up a line. Bottom box: 3 commands to check the version. Source: Perforce advisories, OpenVox GitHub advisories, OpenVox GitHub releases, and Rama Digital analysis.

The version data comes from the Perforce advisory Report Deserialization Code Execution, the OpenVox advisory GHSA-ffr6-rwpm-p5wq, and the OpenVox Server 8.18.0 release. Rama Digital analysis: the OpenVox advisory lists vulnerable versions up to 8.17.0 and 8.31.1, but the 8.17.1 and 8.31.2 releases of 29 October came out before the patch. We treat every version below the safe version as vulnerable.

Open source builds from Perforce stopped at version 8.10.x. The Vox Pupuli blog of 3 November 2026 says that open source Puppet gets no more updates.

Before you start

  • sudo access to the Puppet or OpenVox server that holds the CA.
  • A list of the managed servers (inventory), to compare certificate names.
  • A backup of the /etc/puppetlabs folder, especially the CA folder.
  • Access to the firewall in front of port 8140.
  • A maintenance window of 15 to 30 minutes. Agents cannot reach the server during the restart, then try again at the next run.

Step 1: Check the server and agent package versions

Save this script as check-version-puppet.sh. The script reads the openvox-server, openvox-agent, puppetserver, and puppet-agent packages, and the Puppet Enterprise version.

#!/usr/bin/env bash
# Check the version of Puppet Server, OpenVox Server, Puppet Core, and Puppet Enterprise for CVE-2026-115731 and CVE-2026-115732.
# Data: the Perforce advisories and the OpenVox GitHub advisories, 3 November 2026.
# Use on the Puppet server: sudo bash check-version-puppet.sh
# Use for versions that you copied from other servers: bash check-version-puppet.sh openvox-server=8.17.0 pe=2025.12.0
# Product names: openvox-server, openvox-agent, puppet-agent, puppetserver, pe.
# The script only reads. It changes nothing.

# lower A B: true when version A is below version B.
lower() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }

say() { printf '%-15s %-11s %s\n' "$1" "$2" "$3"; }

check() {
  local p="$1" v="$2" safe=""
  v=${v%%-*}
  if ! [[ $v =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
    say "$p" "$v" "UNKNOWN, compare it by hand with the safe version table"
    return
  fi
  [[ $v =~ ^[0-9]+\.[0-9]+$ ]] && v="$v.0"
  case "$p" in
    openvox-server)
      case ${v%%.*} in 8) safe=8.18.0 ;; 9) safe=9.2.0 ;; esac ;;
    openvox-agent)
      case ${v%%.*} in 8) safe=8.32.0 ;; 9) safe=9.3.0 ;; esac ;;
    puppet-agent)
      # Perforce: open source builds stopped at 8.10.x. Builds 8.11.0 and later are Puppet Core.
      if lower "$v" 8.11.0; then
        say "$p" "$v" "NO PATCH (open source Puppet) -> move to OpenVox 8.32.0 or Puppet Core 8.22.2"
        return
      fi
      case ${v%%.*} in 8) safe=8.22.2 ;; 9) safe=9.1.3 ;; esac ;;
    puppetserver)
      say "$p" "$v" "CHECK: the Perforce patch ships with Puppet Core 8.22.2 or 9.1.3, see the puppet-agent line"
      return ;;
    pe)
      case $v in
        2023.8.*) safe=2023.8.13 ;;
        2025.*) safe=2025.12.1 ;;
        2026.0.*) safe=2026.0.1 ;;
        *) if lower "$v" 2023.8.0; then
             say "$p" "$v" "NO PATCH (old line) -> move to 2023.8.13 or 2025.12.1"
             return
           fi ;;
      esac ;;
    *)
      say "$p" "$v" "UNKNOWN PRODUCT, use openvox-server, openvox-agent, puppet-agent, puppetserver, or pe"
      return ;;
  esac
  if [ -z "$safe" ]; then
    say "$p" "$v" "NO PATCH -> move to a line that still gets patches"
  elif lower "$v" "$safe"; then
    say "$p" "$v" "VULNERABLE to CVE-2026-115731/115732 -> update to $safe"
  else
    say "$p" "$v" "SAFE for these 2 CVEs (safe from $safe)"
  fi
}

if (( $# > 0 )); then
  for x in "$@"; do
    if [[ $x == *=* ]]; then check "${x%%=*}" "${x#*=}"; else say "?" "$x" "Write it as product=version, for example openvox-server=8.17.0"; fi
  done
  exit 0
fi

found=0
package_version() {
  if command -v dpkg-query >/dev/null 2>&1; then
    dpkg-query -W -f='${Status} ${Version}\n' "$1" 2>/dev/null | awk '/install ok installed/ {print $4}'
  elif command -v rpm >/dev/null 2>&1; then
    rpm -q --qf '%{VERSION}\n' "$1" 2>/dev/null | grep -v 'not installed'
  fi
}

if [ -r /opt/puppetlabs/server/pe_version ]; then
  check pe "$(cat /opt/puppetlabs/server/pe_version)"; found=1
fi
for p in openvox-server openvox-agent puppetserver puppet-agent; do
  v=$(package_version "$p")
  if [ -n "$v" ]; then check "$p" "$v"; found=1; fi
done

# Container install: OpenVox is installed as a gem, without packages.
if [ $found = 0 ] && command -v gem >/dev/null 2>&1; then
  g=$(gem list --exact openvox 2>/dev/null | sed -nE 's/^openvox \(([0-9.]+).*/\1/p')
  if [ -n "$g" ]; then
    s=$(puppetserver --version 2>/dev/null | sed -nE 's/.*version: *([0-9.]+).*/\1/p')
    [ -n "$s" ] && check openvox-server "$s"
    check openvox-agent "$g"
    found=1
  fi
fi

if [ $found = 0 ]; then
  echo "No Puppet or OpenVox package found on this server."
  echo "Write the versions by hand, for example: bash check-version-puppet.sh openvox-server=8.17.0"
  exit 1
fi

pid=$(pgrep -f puppet-server-release.jar | head -n1)
if [ -n "$pid" ]; then
  echo "Server process running since: $(ps -o lstart= -p "$pid")"
  echo "Restart the server after the update, so that it loads the new code."
fi
# On the Puppet or OpenVox server
sudo bash check-version-puppet.sh
# Check versions that you copied from other servers
bash check-version-puppet.sh openvox-server=8.17.1 puppet-agent=8.22.1 pe=2025.12.0
# Manual check without the script
puppetserver --version
puppet --version
cat /opt/puppetlabs/server/pe_version
Terminal: the version check reads openvox-server 8.17.0 and openvox-agent 8.31.0 as VULNERABLE. Manual mode: 8.17.1 vulnerable, 9.2.0 safe, Puppet 7.34.0 no patch.
The test container runs the official OpenVox Server 8.17.0 image. The second command uses versions typed by hand. Source: Rama Digital test in a podman openvoxserver:8.17.0 container, 4 November 2026.

In the OpenVox container, the code is installed as a gem, not as a package. The script reads that gem version when no package exists.

Check the result: "VULNERABLE" or "NO PATCH" means go to steps 2 and 3 today. "SAFE" still means run step 5, because the flaws existed before you updated.

Step 2: Check autosign, auth.conf, and port 8140

These 3 settings decide who can use the 2 flaws on your server.

Save this script as check-exposure-puppet.sh. The script reads the autosign setting, the auth.conf rules that compare certificate names, the port 8140 address, and the number of certificates.

#!/usr/bin/env bash
# Check the exposure of a Puppet or OpenVox server for CVE-2026-115731 and CVE-2026-115732:
# autosign, auth.conf rules that compare certificate names, port 8140, and the number of certificates.
# Use on the Puppet server: sudo bash check-exposure-puppet.sh
# The script only reads. It changes nothing.

CONF=/etc/puppetlabs/puppetserver/conf.d
command -v puppet >/dev/null 2>&1 || PATH="$PATH:/opt/puppetlabs/bin"

echo "== 1. Autosign (can anyone get a certificate?)"
a=$(puppet config print autosign --section server 2>/dev/null)
case "$a" in
  true)
    echo "OPEN     autosign = true. The CA signs every certificate request automatically." ;;
  false|"")
    echo "SAFE     autosign = false. An admin approves every certificate." ;;
  *)
    if [ -x "$a" ]; then
      echo "CHECK    autosign uses the policy script $a. Make sure that it refuses names outside your inventory."
    elif [ -r "$a" ]; then
      n=$(grep -cvE '^\s*(#|$)' "$a")
      echo "CHECK    autosign uses the list $a ($n lines)."
      grep -vE '^\s*(#|$)' "$a" | while read -r line; do
        if [[ $line == *'*'* ]]; then echo "OPEN       wildcard pattern: $line"; else echo "CHECK      fixed name: $line"; fi
      done
    else
      echo "SAFE     autosign points to $a, but that file does not exist."
    fi ;;
esac

echo
echo "== 2. auth.conf rules that compare the certificate name with the name in the URL"
if [ -r "$CONF/auth.conf" ]; then
  awk '/allow: *"\$1"/ {f=1} /name: *"/ && f {sub(/^[ \t]*name: */, ""); print "         " $0; f=0}' "$CONF/auth.conf"
  echo "Total: $(grep -cE 'allow: *"\$1"' "$CONF/auth.conf") rules. Vulnerable versions can match these names wrongly."
  other=$(grep -nE '^\s*allow: *"[^"$*]+\.[^"]+"' "$CONF/auth.conf")
  if [ -n "$other" ]; then
    echo "CHECK    custom rules with a fixed host name (check them again after the update):"
    printf '%s\n' "$other" | sed 's/^/         /'
  fi
else
  echo "CHECK    $CONF/auth.conf is not readable."
fi

echo
echo "== 3. Port 8140"
if command -v ss >/dev/null 2>&1; then
  addr=$(ss -ltnH 'sport = :8140' | awk '{print $4}' | sort -u | tr '\n' ' ')
else
  # Without ss: read the kernel socket table. 1FCC is port 8140, 0A is LISTEN.
  addr=$(awk 'FNR > 1 && $4 == "0A" && $2 ~ /:1FCC$/ {split($2, x, ":"); print (x[1] ~ /^0+$/ ? "*" : x[1]) ":8140"}' /proc/net/tcp /proc/net/tcp6 2>/dev/null | sort -u | tr '\n' ' ')
fi
if [ -z "$addr" ]; then
  echo "CHECK    No process listens on port 8140."
elif [[ $addr == *'*'* || $addr == *0.0.0.0* || $addr == *'[::]'* ]]; then
  echo "CHECK    The server listens on all addresses: $addr"
  echo "         Limit port 8140 in the firewall to the agent networks only."
else
  echo "INFO     The server listens on: $addr"
fi

echo
echo "== 4. Certificates"
cadir=$(puppet config print cadir --section server 2>/dev/null)
if [ -d "$cadir/signed" ]; then
  echo "Signed : $(find "$cadir/signed" -name '*.pem' | wc -l)"
  echo "Waiting: $(find "$cadir/requests" -name '*.pem' 2>/dev/null | wc -l)"
else
  echo "CHECK    CA folder not found. Run this script on the server that holds the CA."
fi
sudo bash check-exposure-puppet.sh
Terminal: autosign OPEN, 4 auth.conf rules compare the certificate name with the URL name, port 8140 listens on all addresses, 4 certificates signed.
All values are defaults of the official 8.17.0 image. The x.web-01 certificate is dummy data for step 5. Source: Rama Digital test in a podman openvoxserver:8.17.0 container, 4 November 2026.

In our test, 4 default rules use allow: "$1": catalog, node, report, and facts. These rules allow a node only for its own name, and vulnerable versions can match that name wrongly.

Check the result: write down the autosign status, the number of custom rules with a fixed host name, and the port 8140 address. "OPEN" in part 1 means run step 4a today.

Step 3: Update and restart the server

Only the update and the restart close these 2 flaws. The server reads the new code only after a restart.

# OpenVox on Ubuntu or Debian (tested on Ubuntu 24.04)
sudo apt-get update && sudo apt-get install --only-upgrade openvox-server openvox-agent
# OpenVox on RHEL, Rocky, or AlmaLinux
sudo dnf upgrade openvox-server openvox-agent
# Load the new code. Without a restart, the server keeps running the old code.
sudo systemctl restart puppetserver
# Check again
sudo bash check-version-puppet.sh
# OpenVox in a container: change the image tag, then create the container again
#   ghcr.io/openvoxproject/openvoxserver:8.18.0 or :9.2.0
Terminal: apt-get moves openvox-agent 8.31.1 to 8.32.0 and openvox-server 8.17.0 to 8.18.0. The version check then prints SAFE for both packages.
The test uses official packages from the OpenVox repo. The test container has no systemd, so the restart command did not run. Source: Rama Digital test with official OpenVox packages in a podman Ubuntu 24.04 container, 4 November 2026.

The openvox-server 8.18.0 package in the Ubuntu 24.04 repo needs openvox-agent 8.32.0 or later. So the apt command above moves both packages up. We did not test the dnf command or the restart through systemd.

  1. Puppet Core: update the puppet-agent package to 8.22.2 or 9.1.3 and the puppetserver package from the same release, then restart.
  2. Puppet Enterprise: run the 2023.8.13, 2025.12.1, or 2026.0.1 installer on the primary server, then upgrade the compilers and replicas.
  3. Open source Puppet or OpenVox 7: no patch exists. Use the migration guides in the Vox Pupuli blog of 3 November 2026 to move to OpenVox 8.
  4. Run check-version-puppet.sh again. The correct result: "SAFE for these 2 CVEs" for the server and the agent.

3b. Check custom auth.conf rules after the update

The Vox Pupuli blog of 3 November 2026 asks admins to check custom auth.conf rules. Some rules may depend on the wrong name match of the old versions, so those rules can refuse nodes after the update.

Run check-exposure-puppet.sh again and read the "custom rules with a fixed host name" lines. Then run puppet agent -t on 1 node of each group, and make sure that the catalog arrives.

A 7-row ladder: check the version, check exposure, update, restart and check auth.conf, 2 steps labelled not a fix (turn off autosign, limit port 8140), then signs.
(1) to (3b): the fix order. (4a) and (4b): temporary steps. The red labels remind you that only the update and restart close the flaws. Source: Perforce and OpenVox advisories, the Vox Pupuli blog, and Rama Digital analysis.

Rama Digital analysis: the main patch is on the server machine. Also update the agent package on the other nodes in the next window, so that all machines use the same version.

Check the result: the version script prints "SAFE", the server process started after the update, and every node group receives its catalog.

Step 4: Reduce the impact until the update is done

This step reduces the impact, but it does not close the flaws.

Rama Digital analysis: we chose 2 steps from the workaround list in the Vox Pupuli blog of 3 November 2026. Condition: the update cannot run today. Limit: every managed node has a valid certificate, so 1 broken node can still use CVE-2026-115731.

4a. Turn off autosign and approve certificates by hand

# 4a. Turn off autosign, then reload the server
sudo puppet config set autosign false --section server
sudo systemctl reload puppetserver
sudo bash check-exposure-puppet.sh | head -n 2
# List new requests, check each name in your inventory, then approve them one by one
sudo puppetserver ca list
sudo puppetserver ca sign --certname app-01.example.com
# Revoke an unknown certificate
sudo puppetserver ca clean --certname x.web-01.example.com
# OpenVox container: set the env AUTOSIGN=false, then create the container again
Terminal: autosign is set to false and the server reloads. The script prints SAFE, the app-01 request waits for approval, then the x.web-01 certificate is revoked.
The test container sends a HUP signal to the server process, the same as systemctl reload on a package install. The node names are dummy data. Source: Rama Digital test in a podman openvoxserver:8.17.0 container, 4 November 2026.

The systemd unit from the OpenVox package uses puppetserver reload for systemctl reload. We tested the reload, list, and clean commands. We did not test the ca sign command.

When you need autosign for autoscaling, use a policy script that refuses names outside your inventory. Do not use a wildcard in autosign.conf.

4b. Limit port 8140 to the agent networks

# 4b. ufw example: allow port 8140 only from the agent network 198.51.100.0/24
sudo ufw allow from 198.51.100.0/24 to any port 8140 proto tcp
sudo ufw deny 8140/tcp
sudo ufw status numbered

Replace 198.51.100.0/24 with your server network. We did not test this ufw example. In the cloud, use a security group with the same rules.

Check the result: the exposure script prints "SAFE" in part 1, and port 8140 does not answer from networks outside the list.

Step 5: Look for signs of misuse

These flaws existed before you updated, so also look for signs from the time before the update.

Save this script as check-signs-puppet.sh. The script reads the certificate names in the CA, new certificates, the access log, child processes of the server process, and new files of the service user in temporary folders.

#!/usr/bin/env bash
# Look for signs of misuse of CVE-2026-115731 and CVE-2026-115732 on a Puppet or OpenVox server.
# Use: sudo bash check-signs-puppet.sh [START-DATE] [ACCESS-LOG]
# Example: sudo bash check-signs-puppet.sh 2026-10-01 /var/log/puppetlabs/puppetserver/puppetserver-access.log
# The script only reads. It changes nothing.

FROM=${1:-2026-10-01}
LOG=${2:-/var/log/puppetlabs/puppetserver/puppetserver-access.log}
command -v puppet >/dev/null 2>&1 || PATH="$PATH:/opt/puppetlabs/bin"
cadir=$(puppet config print cadir --section server 2>/dev/null)
signs=0; checks=0

echo "== 1. Certificate names that extend the name of another node (CVE-2026-115732)"
if [ -d "$cadir/signed" ]; then
  mapfile -t names < <(find "$cadir/signed" -name '*.pem' -printf '%f\n' | sed 's/\.pem$//' | sort)
  for a in "${names[@]}"; do
    for b in "${names[@]}"; do
      if [[ $a == *".$b" ]]; then
        echo "SIGN     $a extends the name $b"
        signs=$((signs + 1))
      fi
    done
  done
  (( signs == 0 )) && echo "None."
else
  echo "CHECK    CA folder not found."
fi

echo
echo "== 2. Certificates signed since $FROM"
if [ -d "$cadir/signed" ]; then
  # File time, not notBefore. The CA moves notBefore back by 1 day.
  lines=$(find "$cadir/signed" -name '*.pem' -newermt "$FROM" -printf '%TY-%Tm-%Td %TH:%TM  %f\n' | sed 's/\.pem$//' | sort)
  if [ -n "$lines" ]; then
    printf '%s\n' "$lines" | sed 's/^/CHECK    /'
    checks=$((checks + $(printf '%s\n' "$lines" | wc -l)))
    echo "Compare every name with your server inventory."
  else
    echo "None."
  fi
fi

echo
echo "== 3. One node name from more than 1 IP address (access log)"
if [ -r "$LOG" ]; then
  result=$(grep -E '"(GET /puppet/v3/catalog/|PUT /puppet/v3/report/)' "$LOG" |
    sed -nE 's#^([^ ]+) .*"(GET|PUT) /puppet/v3/(catalog|report)/([^?/ ]+).*#\4 \1#p' | sort -u |
    awk '{ip[$1] = ip[$1] " " $2; n[$1]++} END {for (k in n) if (n[k] > 1) print k " from" ip[k]}')
  if [ -n "$result" ]; then
    printf '%s\n' "$result" | sed 's/^/CHECK    /'
    checks=$((checks + $(printf '%s\n' "$result" | wc -l)))
  else
    echo "None."
  fi
else
  echo "CHECK    Access log is not readable: $LOG"
fi

echo
echo "== 4. Child processes of the server process"
pid=$(pgrep -f puppet-server-release.jar | head -n1)
if [ -n "$pid" ]; then
  kids=$(ps --ppid "$pid" -o pid=,user=,etime=,args= 2>/dev/null)
  if [ -n "$kids" ]; then
    printf '%s\n' "$kids" | sed 's/^ */CHECK    /'
    checks=$((checks + $(printf '%s\n' "$kids" | wc -l)))
  else
    echo "None."
  fi
  user=$(ps -o user= -p "$pid" | tr -d ' ')
else
  echo "CHECK    The server process is not running."
  user=puppet
fi

echo
echo "== 5. New files owned by user $user in temporary folders since $FROM"
# The hsperfdata and jruby-* folders are normal Java and JRuby work files.
files=$(find /tmp /var/tmp /dev/shm -xdev -type f -user "$user" -newermt "$FROM" \
  -not -path '/tmp/hsperfdata_*' -not -path '/tmp/jruby-*' 2>/dev/null)
if [ -n "$files" ]; then
  printf '%s\n' "$files" | sed 's/^/CHECK    /'
  checks=$((checks + $(printf '%s\n' "$files" | wc -l)))
else
  echo "None."
fi

echo
echo "Result: SIGN $signs, CHECK $checks"
if (( signs > 0 )); then
  echo "A sign exists. Revoke that certificate, then follow the response steps in the article."
fi
sudo bash check-signs-puppet.sh 2026-10-01 /var/log/puppetlabs/puppetserver/puppetserver-access.log
Terminal: the signs check finds 1 SIGN (x.web-01 extends the name web-01) and 6 CHECKS: 4 new certificates, 1 name from 2 IPs, and 1 file in /tmp.
The x.web-01 certificate, the IPs in the access log, and the /tmp/.cache-uji file are dummy data. The test had no attack. Source: Rama Digital test in a podman openvoxserver:8.17.0 container, 4 November 2026.
Two columns of signs: in the CA and access log (similar certificate names, new certificates, 1 name from 2 IPs) and on the server (child processes, /tmp files, CA folder changes).
Amber column: signs of CVE-2026-115732. Red column: signs of CVE-2026-115731. Bottom box: the steps when you find even 1 sign. Source: the Neodyme blog, Perforce and OpenVox advisories, and Rama Digital analysis.

The access log does not record the client certificate name. So the script looks for 1 node name in a catalog or report URL that comes from more than 1 IP. An IP change from DHCP also shows up, so the result is CHECK, not SIGN.

Check the result: the script prints "SIGN 0", and every CHECK has an explanation. An attacker can delete logs, so no sign does not mean safe.

When you find even 1 sign, keep the logs and the CA folder as evidence. Revoke the unknown certificates, then replace every password and token in the catalogs of the affected nodes.

When you find a sign in the server processes, treat the CA key as exposed. Make a new CA and new certificates for all nodes with the certificate regeneration guide in the OpenVox or Puppet documentation. Check every node, because the Puppet server controls all of them.

When the servers hold customer data, Indonesian Law 27/2022 on Personal Data Protection requires a written notice within 3 times 24 hours after a personal data breach. Also read the pgAdmin 4 CVE-2026-116241 warning.

Example: 1 morning of response for 3 Puppet servers

This simulation uses dummy data. The server names, times, and results are not customer data.

Time (WIB)Dummy serverCheckedResultAction
08.00puppet-prod (OpenVox 8.17.1, 400 nodes)Steps 1 and 2VULNERABLE, autosign with a wildcardStep 4a now, update this afternoon
08.30puppet-k8s (container 8.17.0)Steps 1 and 2VULNERABLE, default AUTOSIGN=trueTag 8.18.0 and AUTOSIGN=false
09.00puppet-old (Puppet 7.34.0)Step 1NO PATCHStep 4, plan a move to OpenVox 8
09.30puppet-prodStep 5SIGN 1: a certificate name extends db-01Revoke the certificate, replace the database password

Lesson from the simulation: a safe version does not prove a clean server. Step 5 is still necessary on every server.

Who installs the patch

The patch duty differs per service type, so this table helps you know who acts today.

Service typePatch ownerWhat you do
OpenVox or Puppet Core that you install yourselfYour teamSteps 1 to 5
Puppet Enterprise with Perforce supportYour team, with help from PerforceOpen a Perforce ticket when the upgrade fails, then steps 3b and 5
A Puppet server managed by a vendor or MSPThe vendor, per the contractAsk for written version proof, then replace the secrets in your catalogs
OpenVox in a container or KubernetesYour teamChange the image tag, set AUTOSIGN=false, then step 5

Rama Digital recommendation: treat the Puppet server as the master key of all servers. Keep secrets in a secret manager, not in catalogs, and update other panels in the same pipeline, for example for the GitLab CVE-2026-97229 warning. Teams that use system containers should also read the LXD and Incus certificate flaw CVE-2026-112816.

Checklist for today

  1. List all Puppet servers, compilers, and replicas. Owner: platform team. Evidence: a list of names and versions.
  2. Check the version with step 1. Owner: Puppet admin. Evidence: script output.
  3. Check the exposure with step 2. Owner: Puppet admin. Evidence: script output.
  4. Apply step 4 on every server that cannot update today. Owner: Puppet admin and network team. Evidence: script output and firewall rules.
  5. Back up, update, then restart. Owner: Puppet admin. Evidence: script output after the update.
  6. Check custom auth.conf rules and 1 node per group. Owner: Puppet admin. Evidence: puppet agent -t results.
  7. Look for signs with step 5. Owner: Puppet admin and security team. Evidence: script output.

Questions and answers

Has anyone used these flaws in an attack?

On 4 November 2026, we found no attack reports, and the 2 CVEs are not on CISA KEV. But the OpenVox advisory already has a proof of concept, so update today.

My autosign is already off. Am I safe?

No. CVE-2026-115731 needs only 1 valid agent certificate, and every managed node has one. Autosign off only closes the path for an attacker with no node.

Must I also update the agent on every node?

The main patch is on the server machine: the server package and the agent package on that machine. Update the agents on other nodes in the next window.

I still use open source Puppet. What do I do?

Open source Puppet gets no patch. Apply step 4 today, then move to OpenVox 8 or Puppet Core as soon as possible.

Is the OpenVox 8.18.0 container image safe?

Its version is safe for these 2 CVEs. But the 8.18.0 image still uses AUTOSIGN=true by default, so set AUTOSIGN=false or a policy script.

Can a firewall on port 8140 replace the update?

No. Nodes in the allowed networks can still use these flaws. The firewall only reduces who can try.

Do the check scripts change anything on my server?

No. The 3 scripts only read. The commands that change something are only in steps 3 and 4, and each command is written separately.

Next step

The limits that still apply: step 4 does not close the flaws, and the update does not remove access that an attacker already used. When your Puppet server shows signs in step 5, or you need help with an audit and recovery, use Vibe Code Rescue. For 1 urgent problem today, book a 30-minute session.

Sources