{"slug": "remediating-18-openssl-cves-at-scale-with-puppet", "title": "Remediating 18 OpenSSL CVEs at Scale with Puppet", "summary": "Puppet has published guidance on remediating 18 OpenSSL vulnerabilities at scale using its infrastructure automation platform. The June 2026 advisory includes a high-severity heap use-after-free bug in PKCS7_verify() that can lead to remote code execution, with six of the CVEs credited to Anthropic's Mythos model and researcher Alex Gaynor. PuppetDB queries can identify affected nodes, and Puppet tasks or code can enforce patching across large fleets.", "body_md": "Written by[Paul Reed].\n\nThe June 2026 OpenSSL advisory is a big one. [18 vulnerabilities, one rated high severity](https://www.puppet.com/blog/openssl-cve-2026-45447-patching) with remote code execution potential, and a disclosure credited in part to [Anthropic's Mythos model](https://red.anthropic.com/2026/mythos-preview/) working alongside researcher Alex Gaynor. Six of those CVEs trace back to that collaboration.\n\nIf you manage infrastructure at scale, here is how to scope your exposure and get patched cleanly.\n\nCVE-2026-45447 is a heap use-after-free in `PKCS7_verify()`\n\n. The bug fires when OpenSSL processes a PKCS#7 or S/MIME signed message where the `SignedData.digestAlgorithms`\n\nfield is an empty ASN.1 SET.\n\nWhen that happens, OpenSSL frees a `BIO`\n\nobject 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.\n\nAffected ranges:\n\nThe 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.\n\nBefore 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.\n\n```\npuppet query 'inventory[certname, facts.os.name, facts.os.release.full, facts.openssl_version] {\n  facts.openssl_version ~ \"^(3\\.[0-3]\\.|1\\.[0-1]\\.)\" and\n  facts.openssl_version != absent\n}'\n```\n\nThis returns every managed node reporting a vulnerable OpenSSL version, along with OS name, release, and the installed version string. Pipe it to `jq`\n\nfor filtering:\n\n```\npuppet query 'inventory[certname, facts.openssl_version, facts.environment] { \n\n  facts.openssl_version ~ \"^(3\\.[0-3]\\.|1\\.[0-1]\\.)\" \n\n}' | jq '.[] | select(.facts.environment == \"production\")'\n```\n\nYou now have your production blast radius in seconds.\n\nThere are two approaches depending on what you need: an immediate one-time push, or ongoing enforced state.\n\nFor an immediate patch across a set of nodes, the built-in package task gets it done without any code changes:\n\n```\npuppet task run package action=upgrade name=openssl --nodes node1,node2,node3\n```\n\nFor 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.\n\nFor ongoing enforcement through Puppet code, a simple `package`\n\nresource is all you need. No additional module required.\n\n``` js\npackage { 'openssl': \n  ensure => latest, # or pin to the specific version: '3.5.1' \n}\n```\n\nIf you want to lock to the exact patched version rather than `latest`\n\n— which is the safer choice for production since it keeps change controlled — set the version string directly:\n\n``` js\npackage { 'openssl': \n  ensure => '3.5.1', \n}\n```\n\nAdd 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`\n\nrelationship:\n\n``` js\npackage { 'openssl': \n  ensure => '3.5.1', \n  notify => Service['nginx'], \n}\n```\n\nThe `notify`\n\nfires 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.\n\nCreate or update a `security::openssl`\n\nclass. 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.\n\n```\n# site-modules/security/manifests/openssl.pp \n\nclass security::openssl ( \n  String $version = lookup('security::openssl::version'), \n) { \n\n  package { 'openssl': \n    ensure => $version, \n  } \n\n  # Debian/Ubuntu ships libssl separately \n  if $facts['os']['family'] == 'Debian' { \n    package { 'libssl-dev': \n      ensure => $version, \n    } \n  } \n\n  # RHEL/Rocky/Alma ships openssl-libs \n  if $facts['os']['family'] == 'RedHat' { \n    package { 'openssl-libs': \n      ensure => $version, \n    } \n  } \n\n  # Services that need to restart when OpenSSL updates \n  Package['openssl'] ~> Service['nginx'] \n  Package['openssl'] ~> Service['apache2'] \n  Package['openssl'] ~> Service['postfix'] \n  Package['openssl'] ~> Service['sshd'] \n}\n```\n\nPin the version in Hiera. Using a version string rather than `latest`\n\ngives you reproducible, auditable rollouts.\n\n```\n# data/common.yaml \nsecurity::openssl::version: '3.5.1'\n```\n\nWant to vary by OS family? Layer your Hiera:\n\n```\n# data/os/RedHat.yaml \nsecurity::openssl::version: '3.5.1-2.el9' \n\n# data/os/Debian.yaml \nsecurity::openssl::version: '3.5.1-1ubuntu1'\n```\n\nA 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`\n\nmapped in memory until they restart.\n\nConfirm post-patching with `lsof`\n\n:\n\n```\nsudo lsof -p $(pgrep nginx | head -1) | grep libssl\n```\n\nOr simply check the installed version:\n\n```\nopenssl version\n```\n\nIf `lsof`\n\nstill shows the old library path after Puppet ran, check whether the service restart fired by reviewing the Puppet agent log on that node.\n\nAfter the rollout, run a follow-up PuppetDB query to verify closure. This also doubles as audit evidence.\n\n```\npuppet query 'inventory[certname, facts.openssl_version, facts.environment] { \n  facts.openssl_version ~ \"^(3\\.[0-3]\\.|1\\.[0-1]\\.)\" \n}' | jq length\n```\n\nThat number should be zero. If it is not, you have a precise list of which nodes still need attention.\n\nPuppet Enterprise users can pull this same data from the compliance dashboard and export it directly for audit review.\n\nThe `puppet/openssl`\n\nForge 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.\n\nFor 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' }`\n\nhandles natively adds complexity without benefit. Keep it simple.\n\nPuppet 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.\n\nUse 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.\n\n```\n# Defer restarts to a maintenance window using a fact \nclass security::openssl_deferred_restart { \n  package { 'openssl': \n    ensure => '3.5.1', \n  } \n\n  # Only restart if we are in the declared maintenance window \n  if $facts['maintenance_window'] == true { \n    Package['openssl'] ~> Service['nginx'] \n  } \n}\n```\n\nIf 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.\n\nA one-time patch run is not the goal. The goal is a permanent change to your security posture.\n\nYou 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.\n\nFor 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.", "url": "https://wpnews.pro/news/remediating-18-openssl-cves-at-scale-with-puppet", "canonical_source": "https://dev.to/puppet/remediating-18-openssl-cves-at-scale-with-puppet-1abo", "published_at": "2026-06-15 14:10:21+00:00", "updated_at": "2026-06-15 14:36:40.249068+00:00", "lang": "en", "topics": ["developer-tools", "ai-research", "ai-safety"], "entities": ["Puppet", "OpenSSL", "Anthropic", "Mythos", "Alex Gaynor", "PuppetDB", "CVE-2026-45447"], "alternates": {"html": "https://wpnews.pro/news/remediating-18-openssl-cves-at-scale-with-puppet", "markdown": "https://wpnews.pro/news/remediating-18-openssl-cves-at-scale-with-puppet.md", "text": "https://wpnews.pro/news/remediating-18-openssl-cves-at-scale-with-puppet.txt", "jsonld": "https://wpnews.pro/news/remediating-18-openssl-cves-at-scale-with-puppet.jsonld"}}