Written by[Paul Reed].
The June 2026 OpenSSL advisory is a big one. 18 vulnerabilities, one rated high severity with remote code execution potential, and a disclosure credited in part to Anthropic's Mythos model working alongside researcher Alex Gaynor. Six of those CVEs trace back to that collaboration.
If you manage infrastructure at scale, here is how to scope your exposure and get patched cleanly.
CVE-2026-45447 is a heap use-after-free in PKCS7_verify()
. The bug fires when OpenSSL processes a PKCS#7 or S/MIME signed message where the SignedData.digestAlgorithms
field is an empty ASN.1 SET.
When that happens, OpenSSL frees a BIO
object that was passed in by the calling application and is still expected to be valid. The calling application then uses the freed pointer. Depending on heap layout, that results in heap corruption, a process crash, or with a controlled heap grooming primitive, code execution.
Affected ranges:
The other 17 CVEs in the advisory cover authentication bypass via forged certificates (moderate, ~1-in-256 success rate), ciphertext forgery, private key recovery, root CA replacement, and several DoS vectors. None are trivial in regulated environments.
Before touching anything, you want a precise list of affected nodes. PuppetDB gives you this directly from agent-reported facts. No scanner, no spreadsheet, no guessing.
puppet query 'inventory[certname, facts.os.name, facts.os.release.full, facts.openssl_version] {
facts.openssl_version ~ "^(3\.[0-3]\.|1\.[0-1]\.)" and
facts.openssl_version != absent
}'
This returns every managed node reporting a vulnerable OpenSSL version, along with OS name, release, and the installed version string. Pipe it to jq
for filtering:
puppet query 'inventory[certname, facts.openssl_version, facts.environment] {
facts.openssl_version ~ "^(3\.[0-3]\.|1\.[0-1]\.)"
}' | jq '.[] | select(.facts.environment == "production")'
You now have your production blast radius in seconds.
There are two approaches depending on what you need: an immediate one-time push, or ongoing enforced state.
For an immediate patch across a set of nodes, the built-in package task gets it done without any code changes:
puppet task run package action=upgrade name=openssl --nodes node1,node2,node3
For scale, run the task through the Puppet Enterprise GUI and target an entire node group at once. Concurrency limits apply, so large fleets roll out in controlled waves automatically. This is the fastest path when you need to push now and sort out the code side later.
For ongoing enforcement through Puppet code, a simple package
resource is all you need. No additional module required.
package { 'openssl':
ensure => latest, # or pin to the specific version: '3.5.1'
}
If you want to lock to the exact patched version rather than latest
— which is the safer choice for production since it keeps change controlled — set the version string directly:
package { 'openssl':
ensure => '3.5.1',
}
Add this to an existing profile class your nodes already include. No new module, no new class hierarchy, no Hiera restructuring. If services need to restart after the library updates, wire in a notify
relationship:
package { 'openssl':
ensure => '3.5.1',
notify => Service['nginx'],
}
The notify
fires the service restart only after the package actually changes. If the package is already at the right version on the next run, no restart fires. That matters for services where unnecessary restarts are disruptive.
Create or update a security::openssl
class. The core pattern is simple: declare the required package version in Hiera, look it up in the manifest, and subscribe dependent services to the package resource so they restart automatically after the library is replaced.
class security::openssl (
String $version = lookup('security::openssl::version'),
) {
package { 'openssl':
ensure => $version,
}
if $facts['os']['family'] == 'Debian' {
package { 'libssl-dev':
ensure => $version,
}
}
if $facts['os']['family'] == 'RedHat' {
package { 'openssl-libs':
ensure => $version,
}
}
Package['openssl'] ~> Service['nginx']
Package['openssl'] ~> Service['apache2']
Package['openssl'] ~> Service['postfix']
Package['openssl'] ~> Service['sshd']
}
Pin the version in Hiera. Using a version string rather than latest
gives you reproducible, auditable rollouts.
security::openssl::version: '3.5.1'
Want to vary by OS family? Layer your Hiera:
security::openssl::version: '3.5.1-2.el9'
security::openssl::version: '3.5.1-1ubuntu1'
A successful package upgrade does not guarantee the running process is using the new library. Services that were already loaded before the update still have the old .so
mapped in memory until they restart.
Confirm post-patching with lsof
:
sudo lsof -p $(pgrep nginx | head -1) | grep libssl
Or simply check the installed version:
openssl version
If lsof
still shows the old library path after Puppet ran, check whether the service restart fired by reviewing the Puppet agent log on that node.
After the rollout, run a follow-up PuppetDB query to verify closure. This also doubles as audit evidence.
puppet query 'inventory[certname, facts.openssl_version, facts.environment] {
facts.openssl_version ~ "^(3\.[0-3]\.|1\.[0-1]\.)"
}' | jq length
That number should be zero. If it is not, you have a precise list of which nodes still need attention.
Puppet Enterprise users can pull this same data from the compliance dashboard and export it directly for audit review.
The puppet/openssl
Forge module is a legitimate tool for managing PKI entities — keys, CSRs, certificates, DH parameters. If you are already using it for those purposes, there is nothing wrong with it.
For this specific advisory though, it is more than you need. Patching the OpenSSL package is a one-resource job. Pulling in an additional module to do what package { 'openssl': ensure => '3.5.1' }
handles natively adds complexity without benefit. Keep it simple.
Puppet manages the host OS. If your containers bundle their own OpenSSL, you need to rebuild and redeploy those images. PuppetDB facts can help you identify which hosts are running containers that may have bundled the library, but the remediation path is a container pipeline problem, not a package manager problem.
Use Puppet's scheduled tasks or a maintenance window tag in your node group rules to defer the restart to an approved window. The package can update immediately; the service restart can be deferred. This is a better posture than delaying the package update itself.
class security::openssl_deferred_restart {
package { 'openssl':
ensure => '3.5.1',
}
if $facts['maintenance_window'] == true {
Package['openssl'] ~> Service['nginx']
}
}
If you are running OpenSSL 1.0.x or an OS that ships a version, the vendor no longer patches, this advisory is a forcing function.
A one-time patch run is not the goal. The goal is a permanent change to your security posture.
You configure scan frequency – 48X per day by default – Puppet enforces desired state. If a node drifts back to a vulnerable OpenSSL version, whether because of a failed package hold, a reprovisioned VM built from an old image, or a manual change someone made and forgot about, Puppet corrects it on the next run without a ticket or a human in the loop.
For a vulnerability class that attackers are actively targeting within hours of disclosure, that continuous enforcement posture is the actual protection. The patch run will fix the issue one time, continuous enforcement with Puppet keeps you there.