Changelog

Notable changes to PrivacySafe Security Patches, newest first.

2026-11-04

New

  • APT package downloads swapped after verification (CVE-2026-109518, 4 November): apt-nov2026-check.sh. APT can merge two package downloads when an HTTP redirect gives them the same address and reuses a file through a hard link when it retries, so someone who can alter the HTTP traffic (an attacker on the network path, a malicious mirror or a proxy) can replace a package after APT has verified it, and the replacement is installed as root. It needs both a redirect and a retry: Acquire::Retries is 3 by default on Debian 12 and Ubuntu 22.04 and later, so older releases are not exposed unless something turned retries on. check judges the installed APT against the fixed release of its line (3.3.6, 3.2.1, 3.0.4, 2.8.4, 2.6.2, 2.4.15, 2.2.5 and the older lines of the CVE record, and the Ubuntu builds that fix a line below those), reads the effective retry and redirect settings through apt-config, and lists the sources that use plain http; it exits 1 only when the version is vulnerable and both conditions are on. hunt compares cached packages with the SHA-256 in the index and looks for hard-linked files in the package and list caches, and says plainly that no indicator is published. mitigate writes one file in /etc/apt/apt.conf.d that sets Acquire::Retries to 0, confirms the effective value with apt-config and removes the file again if another setting wins; rollback removes it. Updating APT is the fix, and the README gives the update command with retries off for the update itself.

Changed

  • Release 26.11.04-1.0.0 replaces 26.10.30-1.0.0 and covers 2 to 4 November. A script carries the date of its last change: the new APT script 2026-11-04, the 62 other scripts that are new or changed since 26.10.30 (BTCPay Server, eMudhra on macOS, the IPv4 TCP tool and the module blocker, and the 58 that changed for root and sudo handling, the hunts and locking) 2026-11-03, with a new patch version for the 58. php2shell-edge-test.sh did not change and keeps the date of its last change. It only sends HTTP requests, so root does not matter to it.
  • The lock that every script that can change a host takes covers the APT script too, so all 26 of them are covered.
  • nginx and CVE-2026-42533: the nginx README now explains the July script engine flaw (a critical heap overflow, fixed in 1.30.4 and 1.31.3), which this tool's version bar already covers, and a regression that a downstream distribution reported on 4 November in the Red Hat family backport of that fix (some requests answered with HTTP 500, no buffer space in script copy, when a set or rewrite uses a volatile map variable whose value contains variables). nginx-oct2026-check.sh check prints one information line, and changes no verdict or exit code, when the running configuration has a volatile map on a Red Hat family host. The Red Hat advisory index now includes CVE-2026-42533, so the nginx README lists its 10 advisories and the RHEL builds that carry the fix (nginx-1.20.1-28.el9_8.6 for RHEL 9.8, nginx-1.26.3-6.el10_2.7 for RHEL 10.2, the 1.24 module builds for RHEL 8.10 and 9.8, and the 9.2, 9.4 and 9.6 builds of 2 November). We have not reproduced the regression.

2026-11-03

New

  • Red Hat advisories (no new CVE): Red Hat's published list of security advisories for 1 April to 3 November 2026 is matched against the CVEs the collection covers. 37 of the 140 have advisories, 396 in all (41 Critical, 351 Important, 4 Moderate), and 16 tool READMEs now have a Red Hat advisories section with the advisory, the fixed package release and the RHEL stream: bind, brotli, curl, haproxy, jenkins, linux-kernel, nginx, node, openssl, php, pillow, pki, ruby, storybook and tomcat, with php/edge pointing at PHP. The kernel section is a table of ten RHEL streams against the six kernel CVEs, and a list of the fixed kernels for CVE-2026-98432. REDHAT-ADVISORIES.md is the index by tool and by CVE and is a page of the documentation site. The sections are generated by packaging/rhsa/rhsa.py from Red Hat's list (find, verify, fetch, index, docs, audit); edit placement.json or the data, not the marked block. rhsa.py verify also checks an installed package against the fixes, within one stream, one version line and one OpenShift release, and says "check by hand" when it cannot judge. The .deb and .rpm install the checker as /opt/psf-security-patches/rhsa/rhsa.py with the manual page psf-rhsa(8), so a Red Hat family host can run verify --rpm after installing the package; python3 is a recommended dependency of both packages, not a required one, so every other tool still installs and runs without it. verify --list checks against a list downloaded with fetch, and index --all keeps every CVE in a list.
  • Linux kernel IPv4 TCP use-after-free (CVE-2026-98432, 2 November): linux-kernel-ireq-opt-check.sh. A race in the IPv4 TCP handshake that an unauthenticated peer triggers with a SYN carrying IP options; AWS reports remote code execution shown against an MPTCP listener. check judges the running kernel against the fixed release of its branch (5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56, 7.2.10, 7.3), reads the vendor changelog for kernels that backport, and reports the exposure (MPTCP, listeners, tcp_migrate_req, any filter on IP options). hunt looks in the kernel log for the crash side of the flaw. mitigate can switch MPTCP off, drop IPv4 TCP packets that carry IP options (nftables or iptables, scoped to TCP) and block module loading, and rollback undoes it. These reduce the exposure and do not fix the flaw: the fix is a patched kernel and a reboot. It knows the project's default targets first, Debian, Ubuntu, Trisquel and derivatives, and the RPM family next: the update command, the vendor page and the cached apt or dnf updateinfo evidence for the system it runs on, the right package on Amazon Linux 2023 (kernel, kernel6.12 or kernel6.18), needs-restarting -r, nftables instead of an iptables front end that has no u32 match, and a note when a fixed kernel is installed but not running.
  • Linux kernel module blocker (3 November, no CVE of its own): linux-kernel-module-block.sh, a generic tool that stops any kernel module from loading. list shows a table of 42 modules in three labeled groups (recent exploits this collection has a tool for, older well known flaws, and rarely needed attack surface in the CIS style) with what a block breaks and the state on the host; check explains any module by name, including how it autoloads; block writes a file of its own in modprobe.d with a manifest, by name, by group or by CVE; status says whether each block is effective; rollback removes them. It says when a block cannot work: a built-in module (and names the switch), a module that is loaded now (--force, --unload), a module the initramfs loads first (the rebuild command for the system, run only with --initramfs) and a protected list of modules a machine needs to boot. --list-file adds your own rows. The other kernel tools keep their own copies of this logic and stay standalone.

Changed

  • The three Linux kernel tools (linux-kernel-lpe-check.sh, linux-kernel-ireq-opt-check.sh and the new module blocker) take a lock while they change the host. A second mitigate, block or rollback waits up to PSF_LOCK_WAIT seconds (default 30) for the first and then stops with exit 2, so runs started together can no longer overwrite each other's manifest entries. The module blocker also replaces its manifest through a temporary file.
  • The 22 other scripts that change a host take the same lock (drf, drupal, haproxy, joomla, nginx, wp2shell, wordpress-plugins, yootheme, tomcat, rsyslog, vsftpd, emudhra-macos, emudhra-mitigate, collabora-code, btcpayserver, claude-code-vsx, libheif, node, php2shell, php2shell-cpanel, ghostaction and litellm, the last two for quarantine), so all 25 scripts that can change a host are covered. A change command (not a dry run, help or version) re-runs the script under flock -o, so a service the script starts does not inherit the lock, and a tool whose default command changes the host (php2shell-mitigate.sh, emudhra-mitigate.sh) locks when it is run with no command.
  • The Claude Code extension, Collabora, eMudhra (macOS), vsftpd and wp2shell scripts remove their temporary files when they are interrupted, not only when they finish. php2shell-watch.sh expands its log patterns with compgen -G, so a pattern with spaces or no match is handled as it should be. The expat and openapi checkers lost an argument path that was never used.
  • The documentation site and its offline copy have a manual page for psf-rhsa(8), the Red Hat advisory checker that the packages install, next to the 63 for the scripts.
  • The help and READMEs of the BTCPay Server, eMudhra (macOS) and IPv4 TCP tools now list the environment variables they read (BTCPAY_BASE_DIRECTORY, BTCPAY_FRAGMENTS_PROFILE, TOR_SERVICES_DIR, PYTHON_BIN, SITECUSTOMIZE, KERNEL_PROTOCOLS_FILE).
  • haproxy-check.sh check tells a Red Hat 3.0 build about RHSA-2026:83844. Red Hat shipped a fix for CVE-2026-110353 in haproxy-3.0.30-0.1.hum1 although HAProxy gives 3.1 as the first affected line, so a version that check calls outside the range now gets a note when it is a Red Hat build (.hum or .el in the version). The verdict and the exit code are unchanged.
  • The Red Hat row of the Linux kernel table lists the ten fixed streams, and the advisory ids it had are confirmed by Red Hat's own list: RHSA-2026:93414 (RHEL 8.10), 93412 (9.8) and 93413 (10.2).
  • mptcp_diag is no longer treated as part of the flaw. The flawed code is in IPv4 TCP, and mptcp_diag is the module ss -M reads, so blocking it hides the MPTCP listeners that checks look for and removes no known exposure. In the module blocker it moved from the exploited group to the hardening group with no CVE and a new keep flag: --group and --cve skip it with a message, and it is blocked only when named and with --force. In the IPv4 TCP tool --modules all now means mptcp, and mptcp_diag needs its name and --force.
  • The IPv4 TCP tool follows what the vendors and a real kernel showed for CVE-2026-98432. check reads the MPTCP socket count in /proc/net/protocols and says when ss -M lists none while the kernel counts some (the mptcp_diag module that ss -M needs can be missing, as on Ubuntu cloud kernels), and notes that the switch, ss and the count describe one network namespace, since a new namespace starts at 1 whatever the host is set to. mitigate --mptcp says it acted on this namespace only and that sockets already in use survive. mitigate --ip-options adds nothing when a filter of the same kind is in place, such as Red Hat's table in RHSB-2026-016, and rollback leaves it. mitigate --modules says that blocking mptcp_diag only hides MPTCP sockets. The update hints name Ubuntu's -proposed pocket and Red Hat's bulletin, and the README lists the vendor states as of 3 November: Red Hat fixed RHEL 8, 9 and 10 on 1 November, AlmaLinux has the same kernels in production since 2 November, Ubuntu has the generic kernel in -proposed.
  • The IPv4 TCP tool no longer reads "no filter found" while its own filter is active. It piped nft list ruleset and iptables -S into grep -q under pipefail, which fails when grep stops at its first match while the command is still writing; the output is now captured first. The module blocker's echo | grep -q pipelines became here-strings for the same reason.
  • The IPv4 TCP tool recognizes a u32 filter in the form iptables -S prints, and says when an MPTCP listener survives net.mptcp.enabled=0.
  • ShellCheck now runs with nothing hidden: 18 unused variables in 14 scripts are gone, and the blanket shellcheck disable lines no longer include SC2034, so an unused variable shows up as a warning. All 63 scripts have 0 warnings.
  • haproxy-check.sh mitigate --force and php2shell-edge-test.sh --insecure are documented in their READMEs.

2026-11-02

New

  • BTCPay Server hot wallet thefts (2 November, no CVE): the project's advice for a server that ran 2.4.1 or earlier is a fresh server and a new wallet, not an update, and this tool is built around that. check judges the version, recover gives the recovery plan for the host in the project's order with a pre-filled report, and preserve saves logs and records into one redacted archive before the old server goes offline. hunt reads logs for the published indicators, harden reviews ports, fragments, LND routes, the IPv4 options filter and SSH, and tor reports and, on request, enables or disables Tor. --strict makes any warning exit 1 and --json prints the result for monitoring. recover and harden also look for signs on the host: the update history against the project's warning (and hunt reads the update log archives), a legacy unrestricted BTCPay SSH key, custom fragments, local changes or a different origin in the btcpayserver-docker checkout, and server administrators without two-factor sign-in. check flags LND older than 0.21.1, and the plan covers Core Lightning and phoenixd as well as LND.
  • eMudhra on macOS (no new CVE): emudhra-macos-mitigate.sh for the part of the eMudhra intermediate CA compromise that Apple has not fixed. The command line tools that ship with macOS and the Xcode Command Line Tools (curl, git, python3, ruby, perl, openssl) read /etc/ssl/cert.pem without checking revocation, and it still holds the roots the intermediates chain to, as Apple's support page describes; hosted macOS CI images also keep an old Homebrew ca-certificates. check reports both roots in the system and Homebrew bundles, the Homebrew formula version and the pip setting, and --online tests the disclosure site's hosts with curl. apply backs up the bundle, removes emSign ECC Root CA - G3 (and with --include-aaa AAA Certificate Services, which may break unaffected sites), cuts a block only after checking that its certificate has the root's published SHA-256 fingerprint, that the count drops by exactly the roots removed and that OpenSSL can still read every remaining certificate, and puts the original back if its check fails. A bundle OpenSSL cannot read in full is never reported as clean: one damaged certificate makes OpenSSL read none, so check exits 2 and apply refuses. rollback restores it, and refuses when a macOS update has replaced the file. It never runs brew, which refuses to run as root. The README has a step for hosted runners.

Changed

  • The offline documentation site is committed in docs/, generated from the READMEs by packaging/gen-site.py --out docs. The repository can be browsed as it is, and Codeberg or GitHub Pages can serve it from that folder. The packages install the same pages under /usr/share/doc/psf-security-patches/html, and packaging/build-release.sh stops when docs/ is out of date. The manual pages and the info manual are still generated for the packages only.
  • php2shell-watch.sh and php-session-audit.sh now print the date with their version, like every other script. The WordPress plugin checker's help starts with a one-line title, so its manual page reads correctly. The PHP edge guards README names the two PHP2Shell CVEs.
  • Root and sudo are handled the same way in every tool. A command that changes the machine and is started without root now stops and prints the exact sudo command to run, with your own arguments (or says to run it as root when sudo is not installed). A change started from the root account itself, with sudo available, prints a one-line note and carries on: it shows once, only on a terminal, not under sudo, not after sudo su, not in a container and not on a dry run, and PSF_NO_ROOT_NOTE=1 hides it. The Claude Code extension's per-user commands (update, clean, keys) still run as the account that uses the editor. php2shell-watch.sh now names a log it cannot read and exits 2 when none is readable, and php-session-audit.sh and openapi-codegen-check.sh warn a non-root account. Every README for a tool with a state-changing command shows the sudo command, and the README and the how-it-works page explain the rule.
  • Cleanup: about 2,200 lines of unused code are gone. Helper functions that a script defined and never called (the version comparison helpers, distro_mentions, have, and the new root helpers where a script has no use for them) were removed from 55 scripts. The duplicate systemd unit under packaging/ is gone (the build uses php/php2shell-watch.service), as is the unused privacysafe_header_600.png. Every code file carries an Assisted-by trailer, and a script that two models changed carries both lines, the newer last. The trailer of the shell scripts is always the last line.

Fixed

  • A hunt run without root no longer looks clean when it could not read the logs. wordpress-pt and wordpress-wp2shell counted a log they could not open as zero matches and said no strong lead was found; they now say how many logs could not be read and exit 2. The BTCPay Server hunt did the same and also printed a raw shell error; it now exits 2 for an unreadable log or when no log source could be read at all. Eleven more hunts (Brotli, curl, libheif, nginx, Node.js, Ruby, both Grav checks, nginx-ui, YOOtheme and Zammad) now warn a non-root account that a clean result covers only what that account can see.
  • pulsar-check.sh wrote a fixed file name under /tmp, which a local user could turn into a symbolic link while the check ran as root. It now uses a private temporary file, and says when docker cannot be used.

2026-10-30

New

  • Every README ends with the same footer (important notice, support, contributing, more from PrivacySafe, license), kept in step by packaging/readme-footer.py. The documentation site draws a solid, one-color icon for each emoji in the READMEs, and check-site.py now also fails a page with a leftover emoji or a tool page that lacks a part the others have.
  • The documentation is online at privacysafe.app/docs/security-patches, in the PrivacySafe design with the privacysafe.app header, footer and metrics. It has an Atom feed of the newest advisories (feed.xml), a sitemap, and sits in a /docs section beside the PrivacySafe app guides, the VPN setup guide, the Social guide and the 3NWeb protocols. gen-site.py --theme privacysafe-app builds it.
  • A documentation site, built from the READMEs: a home page that starts with the newest vulnerabilities, tools grouped by kind of software, a CVE index, a short guide to how the tools work, a glossary of the terms the pages use, the install guide, one page per tool with an at-a-glance box (who is affected, the fix, the severity, whether it is exploited, which CVEs, which commands only read and which change something, the command to run, and related tools), a filter for tools that can protect the machine, one page per manual page, the changelog and the policies, with a search that works offline. The offline copy loads nothing from other sites and has no tracking. It is built from the READMEs when a release is built, and the packages install it under /usr/share/doc/psf-security-patches/html.
  • Manual pages and an info manual. Every script now has a page (man psf-haproxy-check), there is an overview (man psf-security-patches) and the same material is available as info psf-security-patches. The deb and rpm install them, and their descriptions are rewritten in plain language and list the newer tools. Each page names the AI assistance recorded in the script's header.
  • vsftpd: a check for CVE-2026-119568, the FTPS session ticket flaw, and for CVE-2025-14242, a denial of service in Red Hat family packages. mitigate turns off pasv_promiscuous, can require client certificates and can limit who reaches the FTP ports (see Mitigating it). harden is optional baseline hardening. rollback undoes both.
  • Claude Code extension backdoor: check, hunt, clean-up and a firewall block for the tampered extension that Cursor's marketplace served on 29 October. It works on macOS and GNU/Linux.
    • claude-code-vsx-oct2026-check.ps1 is a read-only check for Windows (PowerShell 5.1 or 7). It reads C:\ProgramData\ssh\administrators_authorized_keys as well, which Cursor's own remediate-windows.ps1 does not clean.
    • clean cuts out only the attacker's text when the key shares a line with a real key, so a real key on that line is kept, and it boots the launchd job out of both the gui and the user domain.
    • update installs the vendor's fixed build after asking. Enter means yes.
    • keys lists your private keys: type, fingerprint, whether a passphrase protects them, and which Host entries in ~/.ssh/config use them.
    • clean saves a record of ~/.ssh before it changes anything. --copy-keys and --drop-agent-keys are optional.
  • Protection with rollback for tools that could only check before: HAProxy, rsyslog, YOOtheme, Tomcat, the Linux kernel local root flaws, Joomla, Django REST framework, Drupal core, WordPress core (wp2shell) and nginx. Each one copies a file before it edits it, checks the result with the service's own checker, puts the original back if the check fails, supports --dry-run, and reloads or restarts nothing unless you ask.
  • WordPress plugins: mitigate deactivates OttoKit, SureCart, LatePoint, Amelia, Profile Builder or Bricksforge when it is below its fixed version. WP-CLI and drush now run as the site's owner, never as root.

Changed

  • The offline documentation site now looks like privacysafe.app. It has the same header, ecosystem card and footer, the same warm dark colors and orange accents, and the same pill buttons, with the logos and icons bundled so it still loads nothing from other sites. It replaces the earlier blue theme. The menu's search box now stays pinned while the menu scrolls, and text meant only for screen readers is no longer visible.
  • The manual pages were checked with man-db, groff, mandoc and GNU info and tightened. Hyphenation is off, so a command or a setting name is no longer split across two lines. The page date is in ISO form, consecutive CVE headline lines stay on lines of their own, and the page for php2shell-mitigate says it runs as root. The page for the PHP2Shell edge tester had "Usage:" as its description. packaging/check-manuals.sh runs these checks, and the release build runs it. The .deb and .rpm no longer carry the repository's .gitignore or an empty .nojekyll file, and the .rpm now provides the old package name.
  • The manual pages, the info manual and the offline documentation site are no longer kept in the repository. The READMEs are the documentation here, and privacysafe.app/docs/security-patches is built from them. packaging/build-release.sh generates the manual pages and the offline site from the scripts and the READMEs, checks them, and puts them into the .deb and .rpm.
  • The packages are now named psf-security-patches (they were psf-security-packages), to match the tools' folder and manual pages. The new .deb and .rpm replace the old package when you install them, so upgrading needs no uninstall.
  • Defaults are decided for you. nginx mitigate with no option applies session tickets off and AIO off. Tomcat mitigate with no option applies the HTTP/2 measure. The measures that can break software (nginx request buffering, the Tomcat AJP change, user namespaces, plugin deactivation) stay opt-in.
  • Every README now uses the same section names, and the wording was rewritten for people. The newest vulnerabilities are at the top of the main README.
  • Release 26.10.30-1.0.0 replaces 26.10.28-1.0.0. Every script carries the date 2026-10-30.

Fixed

  • OpenSSL check: OPENSSL_NO_PACKAGE=1 now ignores the host's package changelog, as documented. Before, a patched system library could make an unrelated local build look fixed.

2026-10-28

Added

  • GitLab security releases: a read-only checker for seven High flaws from the 12 and 21 October 2026 releases: CVE-2026-92883 (cross-site scripting), CVE-2026-16301 and CVE-2026-81839 (pipeline authorization; the second is Enterprise Edition only), CVE-2026-14814 (raw-file cache poisoning), CVE-2026-96893 (a Duo Agent Platform prompt injection, Enterprise Edition only), CVE-2026-15723 (LDAP account takeover when SAML and LDAP auto-linking are both on) and CVE-2026-112212 (OAuth/OIDC discovery cache poisoning). Each flaw is judged on its own ranges. 19.2.9 and the other backports of the 21 October release carry only the regular-expression fix, so only 19.5.1, 19.4.3 and 19.3.6 are a full fix. Reads Omnibus, source and container image versions and the edition.
  • baserCMS 5: a read-only checker for CVE-2026-114621 (admin and admin API SQL injection, CVSS 8.8), CVE-2026-114622 (path traversal) and CVE-2026-114624 (deserialization leading to command execution), published 23 October 2026. Fixed in 5.3.2 and 5.4.2, with 5.0 to 5.2 moving to a fixed release. Reads plugins/baser-core/VERSION.txt, judges each flaw on its own range, and reads the two things the CVE-2026-114621 fix adds so a failed or partial update is named.
  • Jenkins plugin advisory: a read-only checker for the 16 September 2026 advisory, 20 CVEs (CVE-2026-92122 to CVE-2026-92141) in 13 plugins, including eight High sandbox bypasses in Script Security. Reads each plugin's manifest from its directory or its .jpi or .hpi file and compares versions as numbers.
  • OpenSSL DTLS heap disclosure: a read-only checker for CVE-2026-84782 (High, 8.2: DTLS retransmits handshake messages from a stale buffer offset). Judges distribution packages by their changelog and, for Ubuntu, the fixed package versions. Judges local builds and bundled copies by upstream version. Reports DTLS-capable daemons and processes still holding a replaced OpenSSL.
  • Joomla core: a read-only checker for the 29 September 2026 release (5.4.9 and 6.1.4, 16 core fixes), including CVE-2026-90915 (arbitrary directory deletion) and CVE-2026-92222 (SSRF), both rated High. Reads the version constants and the cache-purge fix in the source.
  • Zammad: a read-only checker and hunt for CVE-2026-102489 (session hijack leading to code execution as the zammad user; 6.3.0 to 6.5.4 exploitable) and CVE-2026-102490 (local privilege escalation to root, disputed by the vendor). Both are in CISA KEV (2 October 2026). Reports the remote chain from the version and makes no claim that CVE-2026-102490 is fixed.
  • Linux kernel ebtables SNAT: a read-only checker for CVE-2026-53266 (CVSS 8.8, CISA KEV 18 September 2026). Judges the kernel by its origin: stable-series kernels by upstream version, Debian through uname -v, and Ubuntu, RHEL, SUSE and Amazon kernels by whether the vendor changelog names the CVE, so a distribution kernel is not mislabelled from its release string. Reports ebtables SNAT rules, the ebt_snat module and user namespace settings as information.
  • Linux kernel local root quartet: a read-only checker for DirtyAH6 (CVE-2026-80844), TUNderflow (CVE-2026-81000), PPPoEject (CVE-2026-68121) and DiagSpill (CVE-2026-74469), four local privilege escalations disclosed on 18 September 2026 with public exploits. Each flaw has its own fixed release per kernel branch, so the first stable releases with all four fixes (5.10.270, 5.15.221, 6.1.188, 6.6.157, 6.12.109, 6.18.50, 7.2.4) are not a single rule. Judges the kernel by its origin, reports each flaw separately, and shows whether the needed module and unprivileged user namespaces are present.
  • WordPress page-template path traversal: a read-only checker and hunt for CVE-2026-87902 (CVSS 9.2, CISA KEV 25 September 2026; WordPress 4.7.0 to 7.1.1). Reads the two fixes in wp-includes/template.php independently, so a distribution backport is recognized and a partial patch is named. Falls back to the per-branch version table when the file cannot be read.
  • Magento, Adobe Commerce and Mage-OS: a read-only checker for CVE-2026-103849 (BlockStrap, CVSS 10.0: an unauthenticated email-template engine flaw; Adobe's fix is APSB26-153 of 13 October 2026, Mage-OS 3.6.0). Gives a firm answer for Mage-OS and for dated release strings, and tells you which patch to confirm for other Magento and Adobe Commerce versions, because isolated patches do not change the version string.
  • nginx-ui restricted-directive bypass: a read-only checker and hunt for CVE-2026-84316, CVE-2026-84317, CVE-2026-84318 and CVE-2026-84319 (CVSS 8.3 to 8.8, 12 October 2026): ways around nginx-ui's restricted-directive check for a logged-in user, which run code where nginx loads njs, Perl or Lua. Fixed in 2.4.1. The binary has no version flag, so the script reads a supplied or packaged version and a string the 2.4.1 validator adds to the binary, and reports when they disagree.
  • wp2shell, WordPress core RCE: a read-only checker and hunt for CVE-2026-63030 (REST batch route confusion) and CVE-2026-60137 (WP_Query author__not_in SQL injection), which chain into unauthenticated remote code execution on WordPress 6.9.x and 7.0.x. 6.8.0 to 6.8.5 carries the SQL injection only. Fixed in 6.8.6, 6.9.5 and 7.0.2. Disclosed 17 July 2026, exploited in the wild from 20 July, and in CISA's Known Exploited Vulnerabilities catalog since 21 July. The check judges each flaw from the fix's signature in the core source, so a distribution backport under an older version number is recognized, and it looks for a web server block on both URL forms of the batch route. The hunt reads web server logs, wp-content/uploads and mu-plugins.
  • Incus: a read-only checker for CVE-2026-115375 (instance backup path traversal) and CVE-2026-115377 (symlink in a migration stream), both CVSS 4.0 9.4, and CVE-2026-115372 (restricted project mkfs argument injection). Fixed in Incus 6.0.7, 7.0.2 and 7.5.0. All three let a confined client act as root on the host, so exposure is judged from restricted projects, the unprivileged incus group, trusted certificates and configured remotes.
  • LiteLLM proxy admin escalation: a read-only checker for CVE-2026-114612 (CVSS 9.9: an internal_user can become proxy_admin through reuse of the salt key). Fixes are per release line (1.100.4, 1.101.3, 1.102.2, 1.103.1, 1.104.0-rc.2). It tells the proxy from SDK-only use and points to the existing tool when it meets the malicious 1.107.0. The existing supply-chain section of the README is unchanged.
  • Apache Pulsar and Pulsar Manager: a read-only checker for CVE-2026-105869 (critical: with the MultiRoles authorization provider and a non-empty anonymousUserRole, a forged token can claim any role over HTTP or WebSocket), CVE-2026-105942 (a proxy's credentials used to authorize the original user; needs proxyRoles) and CVE-2026-105944 (Pulsar Manager, CVSS 10, retired: every installation is a finding). Brokers are judged by version and by their configuration. The other twelve advisories in the 21 October announcement are fixed by the same upgrade and are not counted separately.
  • Amazon SageMaker Python SDK: a read-only checker for CVE-2026-112408 (default_bucket() uses a predictable S3 bucket name without checking ownership; fixed in 2.257.3 and 3.10.0). It makes no AWS calls.
  • Sentry for Next.js: a read-only checker for CVE-2026-114139 (the tunnelRoute option makes webpack builds skip middleware for every path under the tunnel path, CVSS 9.1; fixed in 9.47.2, 10.75.2 and 11.0.0-rc.1). Exploitation needs four conditions at once, so it judges tunnelRoute, the bundler, colliding routes and says that middleware being the only authorization control must be reviewed by hand. It was run on a real npm-generated lock, and every fixed version and prerelease tag was checked against the real registry.
  • Jupyter Server and nbconvert: a read-only checker for CVE-2026-114135 (the gateway kernel handler skips the Authorizer's execute check; fixed in jupyter_server 2.6.0) and CVE-2026-114140 (path traversal file write when converting a malicious notebook; fixed in nbconvert 7.17.2). Jupyter Server is judged by configuration, because the bypass needs both a gateway and a restrictive authorizer.
  • Storybook: a read-only checker for CVE-2026-114126 (the dev server exposes its WebSocket token to the local network, letting a LAN host write code into story files; fixed in 8.6.19, 9.1.21, 10.6.2 and 11.0.0-alpha.6). Affected versions listen on all interfaces by default, so it checks for a running dev server on a non-loopback address.
  • Apache Traffic Server: a read-only checker for CVE-2026-111813 (the experimental magick plugin passes client query strings to ImageMagick convert, CVSS 10) and CVE-2026-111811 (the tsmemcache plugin exposes the shared cache without authentication), fixed in 9.2.16, 10.1.5 and 10.2.1. Both flaws only matter when the plugin is loaded, so it reads plugin.config and the remap files rather than looking for files (the only magick.so on a typical host is an ImageMagick coder). It was run on a real Traffic Server 9.2.3.
  • Grav Login plugin: a read-only checker and hunt for CVE-2026-114658 (a page author can create a super user account through an invitation form; affects 3.6.0 through 3.9.12, fixed in 3.9.13). The hunt looks for super-granting invitation frontmatter, pending super invitations and recently changed super accounts, and never prints the invitation tokens or password hashes.
  • Grav core allowlist bypass: a read-only checker and hunt for CVE-2026-116019 (CVSS 8.8: a page editor can run arbitrary public static methods through Flex form overrides; Grav 2.0.15 to 2.2.12, fixed in 2.2.13). Reads GRAV_VERSION and the two things the fix adds to Blueprint.php, so a backport is recognized and a partial update is named.
  • Gitea: a read-only checker for CVE-2026-109553 (SSH certificate principals matched against every stored public key; affects up to 28.1.0, fixed in 28.2.0). It only treats an instance as exposed when it trusts a user certificate authority, as the advisory says, and never prints the CA key.
  • YOOtheme Pro: a read-only checker and hunt for CVE-2026-102785 (CVSS 10.0 unauthenticated remote code execution through the image handler; affects YOOtheme Pro 5.0.0 through 5.0.52, fixed in 5.0.53). It covers the WordPress theme and the Joomla template, hunts for PHP in media and cache directories, PHP inside image files and web log requests that run them, and documents nginx and Apache rules.
  • ISC BIND 9: a read-only checker for CVE-2026-94300 (GSS-TSIG replay memory corruption; fixed in 9.20.30 and 9.21.27; the end-of-life 9.18 branch moves to 9.20). It follows the include chain of named.conf, reports GSS-TSIG settings as a hint rather than proof of safety, and was run against real BIND 9.18.39.
  • rsyslog dynaFile: a read-only configuration audit for CVE-2026-92709 (dynaFile path traversal; no release is listed as fully patched). It finds untrusted properties used in dynaFile paths without secure-path handling, and documents the fix syntax, validated with the real rsyslogd -N1, including that strict mode is a configuration error on older versions.
  • Drupal core Field UI: a read-only checker for CVE-2026-108097 (SA-CORE-2026-014, critical remote code execution reachable through contributed modules or custom code; fixed in 10.6.19, 11.3.19, 11.4.10 and 12.0.0-beta2). End-of-life branches are affected and unpatchable, so they always fail. It reads exported configuration for Field UI, Drupal's published workaround, and checks the contributed Entity List module against its own fixed version on every site, whatever the core version (CVE-2026-108099, fixed in 3.0.10; updating core does not fix it).
  • Apache CouchDB: a read-only checker for CVE-2026-86471 (a server admin can run shell commands as the CouchDB user; fixed in 3.5.3). It reads the release files, the package and optional live servers, reports the bind address, and never prints a value from the [admins] section.
  • Django REST framework: a read-only checker for CVE-2026-114138 (Accept header CPU exhaustion) and CVE-2026-114137 (TokenProxy.delete() silently revokes nothing), both fixed in 3.18.2. It reads installed metadata and lock files, looks for authtoken and TokenProxy in project source, notes the neighboring Django version, and documents a token cleanup and an nginx rule.
  • Craft CMS: a read-only checker for CVE-2026-112777 (Twig create() blocklist bypass leading to code execution for users with template-execution rights; fixed in 4.18.8 and 5.11.0), with a report of allowAdminChanges and the Twig sandbox setting.
  • libexpat billion laughs: a read-only checker for CVE-2026-115506 (entity amplification detected too late; affects libexpat through 2.9.0, fix merged upstream and planned for 2.9.1, not yet released). It reads system libraries, pyexpat in each interpreter and running processes, flags processes running a replaced library, and deep scans for embedded copies.
  • HAProxy HTTP/2 PING: a read-only checker for CVE-2026-110353 (a truncated PING frame pins a worker thread; fixed in 3.4.7, 3.3.17 and 3.2.26). It follows the vendor's release notes, which disagree with the CVE text about the affected range, and audits whether HTTP/2 is reachable, including the implicit clear-text upgrade.
  • WordPress XSS2Shell: a version check, parser-differential XSS reflection probe and defensive audit for CVE-2026-64638, plus a hunt command that searches web server logs and plugin directories for signs of the five-stage XSS-to-RCE chain. The probe is non-destructive: it generates one failed-authentication entry in the auth log.
  • WordPress plugins October 2026: a version check for LiteSpeed Cache (CVE-2026-113500, fixed in 7.9.2), Rank Math PRO (CVE-2026-111862, malware in some copies of 3.0.123) OttoKit (CVE-2026-110372, unauthenticated privilege escalation, fixed in 1.1.41) SureCart (CVE-2026-112241, authentication bypass by spoofing, fixed in 4.9.6), LatePoint (CVE-2026-110653, OTP authentication bypass, fixed in 5.7.5) Amelia (CVE-2026-112327, SQL injection, fixed in 2.4.12) and Profile Builder (CVE-2026-112242, privilege escalation, fixed in 4.0.5), and a hunt for the published indicators of the Rank Math compromise and for signs of OttoKit abuse (new administrator accounts, requests to its REST routes). The hunt reads the database with the mysql client and wp-config.php credentials rather than wp-cli, because wp-cli would run the site's PHP. Adds Bricksforge (CVE-2026-85097, CVSS 10.0, exploited since 7 October 2026: an unauthenticated upload that runs PHP; fixed in 3.1.8.10 and 4.0.1): found by its plugin name, with a hunt for login_admin_*.php and for PHP in the uploads directories. A pre-release of a fixed release now counts as below it.
  • Apache Tomcat October 2026: a read-only checker for CVE-2026-103119 (AJP response mix-up, 11.0.x) and CVE-2026-101356 (HTTP/2 request and response mix-up, 9.0, 10.1 and 11.0). It reads the exact version from lib/catalina.jar and reads server.xml to see whether AJP and HTTP/2 are switched on.
  • Laravel TokenGuard: a read-only checker for CVE-2026-114509 (authentication bypass in the token guard on MySQL and MariaDB; fixed in 12.68.0 and 13.29.0), with a protective middleware printed on request.
  • Pillow 13.0.0: a read-only checker for CVE-2026-96639 (stale image storage reused after mode transitions) and CVE-2026-112900 (EPS filename argument injection into Ghostscript). It reads installed metadata and lock files and reports whether Ghostscript is present.
  • Drupal Webform: a read-only checker for CVE-2026-96355 (template code evaluated from submitted data, SA-CONTRIB-2026-175; fixed in 6.2.12 and 6.3.1), with a configuration scan for custom multiple-value formats.
  • Roundcube Webmail October 2026: a read-only checker for the 1.6.20 and 1.7.5 security release (CVE-2026-114716, one of four mail header injection fixes among 20), and a hunt for CRLF probing of the _uid and _flag parameters in web logs.
  • Next.js Server Actions: a read-only checker for CVE-2026-94482 (denial of service in App Router apps with Server Actions; fixed in 15.5.28 and 16.4.1), with an nginx rate limit on Next-Action requests.
  • openapi-typescript-codegen: a read-only checker for CVE-2026-108551, a code injection in the OpenAPI client generator (0.31.0 and earlier, which is every release). check finds the package in package.json and the npm, yarn and pnpm lockfiles, in installed copies, and in the CI jobs and scripts that run it, and marks a job that fetches its specification from a URL. scan reads generated code and reports any spec-derived value that is not exactly one well-formed single-quoted literal. It never imports or runs anything.
  • Apache .htaccess edge guard: the multipart and cookie guards for hosts without server configuration access. It ships as a dry run, because LogMessage is refused in .htaccess. The dry run adds an X-PHP2Shell-Guard header to what it would refuse. Needs Apache 2.4 with FileInfo and AuthConfig overrides.
  • Edge guard tester: php2shell-edge-test.sh sends benign requests and checks that a guard behaves as described (enforcing, dry run or log-only), including the Content-Type variants PHP parses and look-alikes of an allowlisted path. Needs only bash and curl.
  • Release packages: a source tarball, a .deb and an .rpm, all named psf-security-packages and versioned by release date (YY.MM.DD-X.Y.Z, first release 26.10.28-1.0.0). They install the tools to /opt/psf-security-patches and ship the php2shell-watch systemd unit, pointed at that path and not enabled. Builds are reproducible, and the package metadata carries the CC0-1.0 license and the NOTICE.

Fixed

  • PHP session audit: a risky session serializer (php_binary or php_serialize) on its own, with upload progress off and no runtime switch in the code, exited 0 and printed "No precondition found", although its usage defines exit 2 as a partial match and the same run had already reported condition 1. It now exits 2 and says it is a partial match.
  • libheif checker: the policy check judged a coder blocked when a policy file was written on one line, because one element's rights="none" could vouch for another element's pattern (a delegate or path policy made HEIC look blocked). Each <policy> element is now tested on its own. harden, unharden, mw-harden and mw-unharden put each backup in a directory named only by the second, so two runs in one second overwrote the original backup with an already-edited file. Every backup now has its own private directory (mode 0700, which also keeps a LocalSettings.php copy, with its database credentials, from being world-readable). hunt reported clean when it could read no journal or log; it now exits 2.
  • Collabora CODE mitigator: any option it did not recognize was silently ignored, so a mistyped or invented flag such as restore --dry-run ran a real restore. Only --force is now accepted and anything else exits 2. hunt reported clean (exit 0, with an "ok" line) when there was no app folder and no readable log. It now exits 2 and says nothing was searched.
  • Ruby checker: scan reported a clean result (exit 0) when none of its scan paths existed, and hunt did the same when it could read no journal or log, so a search with no data looked like a clean one. Both now exit 2 and say nothing was read. A Discourse version such as 3.4.0.beta1 was displayed as 3.4.0.. It is now shown in full.
  • Sentry for Next.js, Storybook and Laravel: a pre-release of a fixed release (10.75.2-rc.1, 8.6.19-rc.1, 12.68.0-RC1) was judged fixed, because the status functions compared only the numeric part (and Laravel's norm_ver dropped the suffix). A pre-release comes before the release it leads up to, so it now reads as not fixed. A pre-release above the fixed number, and the fixed release itself, are judged as before.
  • Known limit, stated in the release notes: the checkers that normalize with the shared norm_ver helper compare only the numeric part of a version, so a pre-release of exactly the fixed number can read as fixed in those that were not individually checked.
  • PHP2Shell on cPanel: a release candidate, beta or dev build of a fixed release (8.4.27RC1, 8.4.27beta2, 8.4.27-dev) was judged patched, because the version comparison sorts those after the final release although they come before it, so check exited 0 and cleanup would reduce the drop-in on a build that is not fixed. Such a build is now judged by its numeric part and reads as patched only above the fixed number.
  • eMudhra mitigation: a certificate file moved out of /usr/local/share/ca-certificates or /etc/ssl/certs was kept under its bare file name, so two different files with the same name from different directories overwrote each other in the backup and restore would then have put the wrong certificate back. Each is now kept under a numbered name. Backup directories were named only by the second, so two runs in one second shared one and the second overwrote the first run's copy of ca-certificates.conf. The process id is now part of the name.
  • PHP2Shell mitigation: remove backed up the AppArmor profile, your local override and the disable link into one directory under the same file name, so each copy overwrote the previous one and the site-specific rules in the local override were lost (with a disable link present, only the link survived). Each now has its own name. Backup directories were named only by the second, so two runs in the same second overwrote the first backup. The process id is now part of the name. The message printed when the generated AppArmor profile fails to parse said "nothing installed" although the ini drop-in from step 1 had already been written. It now says what was and was not changed. The undocumented PHP_VER environment variable, which limits a run to one version like --php, is documented.
  • Eight more checkers read running containers. LiteLLM, SageMaker, Apache Traffic Server, YOOtheme, the WordPress plugins checker, nginx-ui, Apache Pulsar and the OpenAPI codegen checker now look inside running docker and podman containers (SCAN_CONTAINERS=1, the default), by the same read-only probe and per-container isolated run, and each sends only what it reads: LiteLLM sends a one-line marker for each proxy config and only the matched image tags, so API and master keys stay in the container. Traffic Server blanks every line that does not load magick.so or tsmemcache.so. Pulsar sends only the four authorization settings it judges. The WordPress plugins checker sends only the Plugin Name and Version header lines and the file's modification time (Rank Math PRO's arrival-time test then uses the modification time alone and says so). Nginx-ui runs its two binary-signature tests inside the container instead of copying a 40 MB binary. LiteLLM and Pulsar also search /app and /pulsar, where the official images keep their configuration. The shared staging helper no longer depends on a have() function the script may not define (the OpenAPI checker has none, which made staging silently skip).
  • Thirteen more checkers read running containers. Next.js, Sentry for Next.js, Storybook, Laravel, Django REST framework, Jupyter, Pillow, BIND, HAProxy, Tomcat, CouchDB, libexpat and Gitea now look inside running docker and podman containers (SCAN_CONTAINERS=1, the default). The JavaScript, PHP and Python checkers copy only the project files they need through a read-only probe (route directory names only for Sentry; .env is never copied, only its DB_CONNECTION line). The daemon checkers capture the daemon's version output and its configuration, with secrets (TSIG and RNDC keys, HAProxy passwords, keystore passwords, CouchDB admin entries, the Gitea CA key and every other app.ini value) redacted inside the container before anything is copied, then run once per container in isolation. Pillow judges a container's copy by that container's own Ghostscript. A staged symlink is never kept (docker cp copies the link itself, which the host-side check would then have followed). The same protection was added to the ten checkers that read containers earlier. The statements in the Tomcat and Gitea READMEs and --help that containers cannot be inspected were corrected.
  • Web-application checkers now read installs inside running containers. The WordPress path traversal, Joomla, Magento and Mage-OS, baserCMS, Grav core and Login, Roundcube, Craft CMS and Drupal core and Webform checks find installs in running docker and podman containers by their marker file, copy only the files they read into a private temporary directory, and judge them like host installs (SCAN_CONTAINERS=1, the default). They run it once in the main shell and pass the copies on as ordinary directory arguments. Nothing is run in a container beyond find and a file copy, and .env and wp-config.php are never copied.
  • Running containers are now checked by OpenSSL, Jenkins and Zammad. With SCAN_CONTAINERS=1 (the default) the OpenSSL checker asks each running docker or podman container for its own distribution, package and openssl version and judges it by the container's rules (Ubuntu's fixed builds per release, the container's changelog, the upstream version where there is no package database). The Jenkins checker reads the advisory plugins' manifests inside containers. The Zammad checker reads VERSION under /opt/zammad and /usr/share/zammad, and never judges a container by the host's package version. They join GitLab, nginx-ui, Pulsar, LiteLLM, curl, nginx, Plex and others that already looked inside containers.
  • Every README now has a "know before you run it" section. The 33 that lacked one (the newer checkers and a middle group of older ones) now list what the tool is and is not, drawn from each script's own stated scope: the fixed versions, what a version number cannot show, what it reads, which flaws are conditional, and where a hunt exists or does not.
  • LiteLLM checker: a scan path that exists but cannot be read left check reporting clean (exit 0) although its usage promises exit 2 for something it could not read. It now exits 2 when nothing else is found.
  • GhostAction checker: check reported itself as a C2 indicator (exit 1, "credentials may be stolen") when run from the directory it was downloaded to, because its own text names the domains. It now leaves its own file out. It also reported a clean result (exit 0) when every scan path was missing, and its usage promises exit 2 for something it could not read: that is now implemented. hunt wrote crontab -l output to a fixed file in /tmp. It now uses a private temporary file.
  • Plex checker: hunt reported any copy of its own script found under a scanned scratch directory. It now leaves out the running file. New HUNT_ROOTS setting for the directories hunt searches.
  • Node.js mitigator: status exited 1 when the installed guard matched the shipped one, which is the healthy case; it now exits 0.
  • Runtimes found only on PATH were never found. The curl, nginx, Ruby and Brotli (Node.js scan) checks and the Node.js mitigator searched PATH with command -v -a, which is not a valid option in bash and failed silently, so only a fixed list of locations was searched. A Node.js, Ruby, nginx or curl installed elsewhere on PATH was reported as not present. They now use type -a -P.
  • Pre-releases and non-version text were judged fixed. In the Brotli checker, a node that printed v24.22.1-pre, -rc.0 or -nightly… was reported fixed at the fix version, and one that printed text (an error from a broken version-manager shim) was reported fixed because non-numeric text sorts after every number. The same function judges Bun, Envoy, Istio, cdxgen and the Ruby and PHP Brotli extensions. A -dev suffix was also damaged before it was judged (every v was removed from the output). All of these are now reported as unknown. The Node.js mitigator dropped a pre-release suffix the same way and now keeps it. A runtime it cannot judge on disk makes check exit 2, as its README says.
  • One unreadable binary could end a whole check. Under set -e, an unreadable curl (or a node that printed no version) made the curl check and the Node.js mitigator stop silently after a few lines with exit 1, so later findings, such as a vulnerable libcurl, were never reported and the status looked like "vulnerable". The PKI mitigator did the same, with exit 3, when openssl failed during its certificate self-test. All three now report the problem and finish.
  • curl checker: a development build (8.20.2-DEV) at a fix version was reported as fixed. It is now reported as unknown, because it is a build made before the release.
  • WordPress XSS2Shell checker: check and audit now stop with exit 2 when no URL is given, instead of relying on curl failing. audit reported a clean result (exit 0) for a site that did not answer. An unreachable target is now an error (exit 2), as its usage text always said. Its HTTP response bodies, which can contain an exposed wp-config.php, were written to fixed, world-readable files in /tmp and left behind when a run was interrupted. It now uses one private mktemp file that is removed on every exit path.
  • Exit codes. Ten check and mitigate tools (Brotli, curl, GhostAction, libheif, LiteLLM, nginx, Plex, Ruby, the Collabora CODE mitigator and the Node.js mitigator) exited 1 on a fatal error such as a mistyped command or an unknown option, although their READMEs document 2 for errors and 1 means findings. They now exit 2. The PKI and PHP mitigators keep their own documented exit codes.
  • Environment variables that several tools read but did not list (the Brotli per-product fixed versions, BACKUP_ROOT, SCAN_CONTAINERS, GUARD_SRC, SCAN_DEPTH, CODECS_SINCE_DAYS) are now listed in each tool's --help. Two advisory links in the WordPress README pointed at a page GitHub does not serve. They now use the repository advisory address. An unused helper was removed from the Node.js mitigator.
  • Pipelines that could miss a match. Under set -o pipefail, producer | grep -q PATTERN can report "not found" when grep -q exits at its first match and the producer dies of SIGPIPE. It needs a producer that writes more than a pipe buffer (about 4 KB) after the match. Twelve scripts used the shape (bind, brotli, curl, drupal, heif, nginx, node, both PHP2Shell tools, pki, and both WordPress checkers), several of them against large sources: journalctl in the Node guard-liveness check, rpm -q --changelog in the cPanel PHP tool, openssl s_client in the PKI tool, and HTTP response bodies in the WordPress probes. Each now reads all of its input and discards the output. What the scripts report is otherwise unchanged.
  • Inputs that could pass as clean. In the checkers for Zammad, OpenSSL, the Linux kernel, Jenkins, GitLab, Magento and Mage-OS, an input the script could not trust to be a release no longer ends in a clean result. A pre-release build (7.2.0-pre, 3.6.5-dev, 3.6.0-rc2, 7.0.13-rc1, 19.4.2-pre) at or above a fix boundary is a warning, not the fixed release. An unparsable package version or openssl output, and a Jenkins plugin whose manifest has no Plugin-Version, are reported as unreadable (exit 1) and are never skipped. A pre-release below a fix boundary is still judged exposed.
  • nginx-ui checker: checks the official uozi/nginx-ui container image by tag, and judges a version supplied in NGINX_UI_VERSION even when no binary is found on the host. Both were previously ignored, which let a containerized or remote instance report as clean. A latest tag is a warning that says the version cannot be read from outside the image.
  • Node.js tool: hunt exited 1 on a host with nothing to find, although 1 means "findings". A RETURN trap that referenced a function-local variable fired again after the variable went out of scope, and set -u aborted the script. A hunt with nothing to report now exits 0.

Changed

  • PHP2Shell Apache edge guard (apache-php2shell-guard.conf v3): log-only mode now logs a POST that Apache resolves through a directory index, such as a POST to /. A LogMessage inside an <If> section was not written for those requests, so log-only reported nothing for a request that enforcing then refused, on the path the confirmed chain used. The log line now uses LogMessage's own expr= condition. Found by the new tester and verified on Apache 2.4.58; enforcement was not affected.
  • PHP2Shell edge guards: records which Content-Type values PHP parses as multipart (as parsed by PHP 8.3.6) against what both guards match. Every value PHP parses starts with multipart/form-data, so the guards need no change.
  • PHP2Shell hunt: notes that a double-free crash on 8.4.27 or 8.5.12 may be php-src issue 24488, a SimpleXML garbage-collector bug, and is not by itself evidence of exploitation.
  • Statement of scope: 57 scripts addressing 139 CVEs across 67 software projects and 10 programming languages, plus 4 other global security incidents.
  • Brotli checker: 1.3.0 reports the Ruby brotli gem (fixed 0.9.1) and the PHP brotli extension (fixed 0.21.1), lists running processes that have libbrotlidec loaded and flags any still running a library replaced on disk (restart needed), and notes dotnet, duckdb and clickhouse when found on PATH, because they bundle an unfixed copy. That last list is informational and does not change the exit code.
  • Brotli documentation: corrects the description of the bug (an unchecked return value in DecodeBlockTypeAndLength() that lets a crafted stream write past an eight-byte buffer; affected range 1.0.3 through 1.2.0; the fix returns error -17), adds the Broccoli2Shell name, the three ways untrusted Brotli arrives (fetch, request bodies, direct calls), Python specifics and the scanner gap, which language ports are unaffected, exploitation status, and a survey of projects that bundle their own copy (.NET, DuckDB, ClickHouse, Arrow, the 7-Zip forks and more), with what is fixed and what is not as of 25 October.
  • PHP session audit: records the PHP Foundation's 23 October position on CVE-2026-115560 (full advisory with the next release, affected versions and severity not yet published, no exploitation reported, keep session.upload_progress.enabled off), and corrects one table cell that wrote the directive as session.upload_progress without .enabled.
  • Node.js guard documentation: adds the request-body path (Express 5.0.0 to 5.3.0, body-parser 2.0.0 to 2.3.0), how to read the bundled Brotli version, Imperva's reproduction on the official Node.js 24.22.0 image, and the restart and rebuild reminder.

2026-10-24

Added

  • PHP2Shell on cPanel: check, drop-in, status, restore and cleanup for EasyApache 4 PHP (ea-php74 to ea-php85) under any handler, with patch detection and a check that the web server and FPM workers have restarted.
  • PHP2Shell edge guards: Apache and nginx configuration that refuses multipart POSTs to paths you have not allowlisted (log-only first), cuts an oversized Set-Cookie on Apache, and documents request body and response header limits for both servers.
  • PHP2Shell detection rules: an auditd rule, a Tetragon filter and a log-only outbound-connection rule that record every program the web-server account starts, with a guide to how strong each signal is.
  • curl HTTP/2 upload: check for CVE-2026-92392 (fixed in 8.23.0; branch backports 8.14.3 / 8.16.2 / 8.20.2).
  • nginx October 2026: check and nginx -T config audit for the five flaws fixed in 1.30.6 / 1.31.7, plus the two njs fixes in 1.0.2.
  • Nextcloud Collabora Built-in CODE: mitigation, check and hunt for CVE-2026-110447 (exploited in the wild). Update to 26.4.303 or remove; disabling is not enough.
  • LiteLLM 1.107.0 supply-chain compromise: detect, hunt and contain the malicious release (no CVE). Pin 1.106.2 and treat an affected host as compromised.
  • Plex Media Server Plex2Shell: check and hunt for the RCE chain (CVE-2026-96651/96652/96654/96655/96656) fixed in 1.43.3.10861, exploited in the wild to drop miners and a proxy backdoor.
  • GhostAction developer-machine stealer: scan, hunt and contain the stage-2 stealer hidden in a fake fa-solid-400.woff2 and a VS Code folderOpen task (no CVE, exploited in the wild).

Changed

  • PHP2Shell edge guards: notes on how the guards behave across stacks. mod_proxy_fcgi rejects a leak-sized response on its own before the guard runs, proxy_buffer_size behaves the same as fastcgi_buffer_size, and HTTP/2 cannot run under mod_php because mod_http2 declines to operate under the prefork MPM.
  • PHP2Shell detection rules: the nftables egress rule was loaded on a live system and logged a real DNS lookup and outbound connection made by the web account, and did not log the same traffic from another account.
  • PHP2Shell hunt: the hunt baseline moved from the 21 October patch date to 17 October, the earliest exploitation date reported (by Amazon Threat Intelligence), so a quiet-period comparison is more likely to be quiet. Set HUNT_SINCE to override (php2shell-mitigate.sh 5.2.5).
  • PHP2Shell hunt and watch: indicators from a 24 October honeypot report, including both forms of the numbered-request fingerprint, leak-sized response leads, nginx's upstream sent too big header error, TCP peers of flagged processes, an attempt-only address list, proof files, and auditd events (php2shell-mitigate.sh 5.2.4, php2shell-watch.sh 1.4.3).
  • PHP2Shell exploitability summary: a sourced summary of what PHP validated (remote code execution over mod_php for CVE-2026-109618 and over PHP-FPM for CVE-2026-109619, a likely first exploitation attempt on 17 October, reported by Amazon) and PHP's protective measures with their costs. The AppArmor layer does not reach mod_php.
  • Help and version handling: every script accepts --help, --version and version, and rejects an unknown command.
  • Brotli checker: 1.2.0 also detects Python brotlicffi (fixed 1.2.1.0) and cdxgen (fixed 13.5.0 / 12.8.9), and recognizes Debian 13's fixed brotli build.

2026-10-23

Added

  • Brotli checker: CVE-2026-111497 is not only a Node.js bug. The checker covers libbrotli on the host, the runtimes that bundle it, and containers.
  • libheif checker: the HEIF/AVIF decoder heap overflow used to breach a Discourse forum. Read-only check plus ImageMagick and MediaWiki hardening.
  • Ruby and GitLab checker: the Regexp fixes in Ruby 4.0.9 / 3.4.12 / 3.3.13 and the four GitLab CVEs. If you run GitLab, upgrade it first.

Changed

  • PHP2Shell: CVE-2026-115560 (session upload progress) is not fixed by the 21 October releases. v5 keeps upload progress off after cleanup. If you ran cleanup with v4, re-run it with v5.
  • php2shell-watch: adds two ReliaQuest detection signals for CVE-2026-109618 (in Unknown on line 0, and a POST-500 / same-PID crash correlation).

2026-10-22

Changed

  • PHP2Shell: an allocator double-free reproduced on 8.5.12 (the release listed as fixed) via php -S. hunt and the watcher now detect it. The AppArmor layer confines PHP-FPM, not the php CLI.

2026-10-21

Added

  • PHP2Shell mitigation: php2shell-mitigate.sh for CVE-2026-109618 and CVE-2026-109619, written the night of 20 October, after exploit details leaked, then tested and published the morning of 21 October, before PHP published its fixes (announced for 14:00 UTC) and before distributions shipped theirs. Adds two layers to every installed PHP-FPM from 7.0 through 8.5: an ini drop-in that turns off session.upload_progress, and an AppArmor profile, built from each version's own FPM config, that stops FPM from executing shells and other binaries. It syntax-checks each profile, tests FPM with a config check, restart, reload and socket check, and falls back to complain mode or unloads the profile if FPM does not come up.
  • PHP2Shell patch detection: added when PHP released the fixed versions (8.2.35, 8.3.36, 8.4.27 and 8.5.12) at 14:00 UTC (10:00 Eastern). The report, status and cleanup say whether each installed PHP package is fixed, by upstream version, by known distribution backport, or by a package changelog that names both CVEs, and flag FPM or Apache still running the old binary after an update. cleanup removes the ini drop-in once patched and leaves the AppArmor profile loaded.

The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.

The PrivacySafe Ecosystem

Shop at Ivy Cyber

Ivy Cyber offers PrivacySafe products and services in the USA and has partnerships with 3NSoft in Canada and Kuip Limited in the European Union. Enterprise services and consulting are available at ivycyber.com

PrivacySafe Foundation is a 501(c)(3) nonprofit public charity that supports the development of the PrivacySafe applications, along with the components they rely upon: 3NWeb protocols, filesystem, documentation, and reference implementations.

PrivacySafe Foundation

Donations to PrivacySafe Foundation support education, research, documentation, and public code. Purchases from Ivy Cyber support PrivacySafe products, services, hosting, and technical support. Together, we're building a better web.