Independent AI intelligence Two editions daily · ET
Fervor AI

Analysis · October 22, 2026 · repo

openai/php2shell-checkerPHP2ShellCodexagent-securitycodexai-skills

OpenAI's php2shell-checker Can Flag a Vulnerable PHP Server. It Can't Prove You're Safe.

A free, tiny scanner for this week's critical PHP bug, how to read its three answers, and why "UNKNOWN" is the most honest thing it says.

The most dangerous answer a security scanner can give is "you're fine."

A scanner that says "vulnerable" sends you to patch. A scanner that says "fine" sends you to lunch. If that second answer is wrong, nobody looks again until something breaks.

This week gave that problem a very specific shape. On October 21, PHP shipped 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to fix two critical bugs that "can allow unauthenticated remote code execution," nicknamed php2shell. Alongside it, OpenAI published php2shell-checker, a small open-source tool that tells you whether a server runs a vulnerable PHP version. It sat at #6 on Trendshift's daily GitHub board when I read it at about 7:15 a.m. ET on Thursday, October 22.

The interesting part is not that it finds vulnerable servers. It is how carefully it refuses to promise a server is safe. That refusal is a design lesson for every security check you hand to an AI agent.

What php2shell is, briefly

PHP is the language behind a large share of the web: WordPress, Drupal, countless internal admin panels. The two bugs, CVE-2026-109618 and CVE-2026-109619, live in the PHP engine itself, not in any one app.

In plain terms, according to the PHP Foundation's write-up: if a request hits PHP's memory limit at exactly the wrong moment, PHP can leave half-built internal data lying around and later treat it as real. That corrupts memory, and corrupted memory is how attackers run their own code. The Foundation says "full remote code execution was demonstrated with default settings" on Apache with mod_php (PHP running inside the Apache web server), and confirmed on PHP-FPM (PHP running as its own pool of worker processes behind a web server like nginx) through application code paths.

Wiz, one of the reporting teams, puts it more bluntly: "even a simple 'Hello World' application running on an affected PHP version can be vulnerable," and "Nearly every PHP version is affected" back to 5.4 (Wiz). The release was even pulled forward a day, to October 21 at 14:00 UTC, after the CVE IDs leaked, per the Foundation.

So the question every team with a PHP server asked this week was simple: are we on a fixed version?

What the checker actually does

php2shell-checker is two Codex skills plus one Node.js script, released under the Apache-2.0 license. A skill here means a packaged set of instructions an AI coding agent (Codex, OpenAI's agent) can load and follow.

  • phpcheck-url is for when you only have a website address.
  • phpcheck-server is for when you can log in to the machine.

Under the hood, both use the same simple idea. For a URL, the script sends a HEAD request (asking for headers only, no page body) and reads two response headers, X-Powered-By and Server, where many servers announce their PHP version. It falls back to a GET only if the server rejects HEAD, and it does not follow redirects or look at page content. For a local machine, it runs php -v.

Then it compares the version to a table of cutoffs baked into the script. For the supported branches, the cutoffs are the last vulnerable patch: 8.2.34, 8.3.35, 8.4.26 and 8.5.11. Anything at or below the cutoff is vulnerable. The table reaches back to PHP 5.4.

It gives one of three answers, each with its own exit code so scripts can act on it:

  • VULNERABLE (exit 1): the version is at or below the cutoff for its branch.
  • OK (exit 0): a stable release above the cutoff.
  • UNKNOWN (exit 3): no version found, a branch not in the table, or a prerelease build such as an rc (release candidate, a test build before the final version).

The asymmetry nobody puts on the box

Here is the part worth slowing down for. These three answers do not carry the same weight.

VULNERABLE is a strong signal. If a server openly says it runs PHP 8.3.30, treat it as exposed and patch, unless you can show the package carries a backported fix (more on that below).

OK is weaker than it sounds. The README is explicit that OK is a version comparison. It assumes later patches in the branch include the fix, but it does not test the fix itself.

UNKNOWN is the common case on well-run servers. Many teams hide their PHP version on purpose, because advertising it helps attackers. PHP has a setting for exactly this, expose_php, and plenty of hosts strip the X-Powered-By header at the proxy. Those servers return UNKNOWN. The README says plainly that UNKNOWN does not mean a system is patched.

There is one more trap, and it cuts the other way. Linux distributions often backport security fixes: they copy the fix into the older version they ship instead of moving you to the new version number. A Debian or Red Hat package might report a version string below the cutoff and still carry the fix. The checker would call it VULNERABLE. The server skill tells the agent to check "the full package revision" (the distribution's own build number tacked onto the PHP version) against vendor advisories for exactly this reason.

So the real map looks like this. Headers can tell you that you are probably exposed. Headers cannot prove you are safe. Only the package you are actually running, checked against your vendor's advisory, can do that.

Why this is the right design for agent tools

Think about what an AI agent does with a tool's answer. It summarizes. It moves on. If a tool only knows two words, "vulnerable" and "fine," then every hidden header and every odd build gets squeezed into one of them, and the agent will report it with confidence.

A third answer gives the agent somewhere honest to put "I could not tell." That one design choice prevents the worst failure mode: a cheerful green summary of a server nobody actually checked.

The server skill adds two more good habits. It tells the agent to confirm that the PHP it checked is the one actually serving requests (the command-line php and the PHP-FPM process can be different builds), and it says outright: don't run exploit probes. If the version cannot be verified, report unknown (SKILL.md).

If you are building your own agent security checks, copy all three: a third state for "can't tell," a rule to verify the thing that is actually running, and a hard line against poking production with exploits.

Put this into practice

You need Node.js and a shell. The repo does not state a minimum Node version, and the script uses built-in features such as fetch, so use a current release. There are no tagged releases either, so note the commit you ran if you want a record of what checked your fleet. Clone the repo first:

git clone https://github.com/openai/php2shell-checker
cd php2shell-checker
git rev-parse HEAD   # the exact commit you are running

Check a site you own:

node phpcheck.js "https://your-site.example"
echo $?   # 0 = OK, 1 = VULNERABLE, 3 = UNKNOWN, 2 = bad arguments

Check the machine you are on:

node phpcheck.js --local

Check a list of sites and keep machine-readable output:

while read url; do
  node phpcheck.js --json "$url"
done < sites.txt > php-status.jsonl

The --json flag reports a patch_status of ok, vulnerable or unknown for each one.

Inside Codex, install both skills with the command from the README:

$skill-installer install both skills from https://github.com/openai/php2shell-checker

Then handle each answer:

  • VULNERABLE: upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12 (or your vendor's fixed package). Wiz is clear that "Applying the PHP security update remains the only complete remediation."
  • UNKNOWN: log in and look at the package, not the header. On Debian or Ubuntu that usually means dpkg -l | grep php; on Red Hat-family systems, rpm -qa | grep php. Compare the full package revision against your distribution's security notice.
  • OK: if the server is yours, confirm once on the box anyway. Make sure the PHP-FPM or Apache module serving traffic is the same build that php -v reports.

If you cannot patch today, put something in front of the server. Cloudflare's new response rules, available on every plan including Free, ship managed protections aimed at php2shell, and Wiz lists signals worth watching: unusual multipart requests followed by memory-limit errors, repeated HTTP 500 responses, oversized PHPSESSID cookies, and unexplained PHP worker restarts (Wiz). Treat both as a bridge to the patch, not a replacement for it.

Honest limitations

  • It only reads version strings. It never tests whether the bug can be triggered. That is a feature for safety and a limit for certainty.
  • False alarms on backported packages. Distribution packages with fixes backported can show up as VULNERABLE.
  • Hidden headers give you nothing. On a well-hardened server, the URL mode will usually say UNKNOWN, which pushes you back to logging in.
  • The cutoff table is a snapshot. The server skill dates its table October 20, 2026. Any future PHP branch returns UNKNOWN until someone updates it.
  • No outside code contributions. OpenAI accepts bug reports and feedback through issues but not pull requests, so fixes depend on OpenAI's pace.
  • Do not confuse it with the exploit repos. Other repositories trending beside it ship working exploit chains. This one deliberately does not, and you should not run those against anything you do not own.

The takeaway

Run the checker on everything you own today. It is free, it takes seconds, and every VULNERABLE line is a real to-do.

Then take the bigger lesson with you. When you hand security work to an agent, the tool's vocabulary decides what the agent can honestly say. Give it a word for "I don't know," and it will stop telling you everything is fine.

Sources: openai/php2shell-checker README and phpcheck.js and phpcheck-server SKILL.md on main, read October 22, 2026 (no tagged releases); php.net release announcements, October 21, 2026; PHP Foundation security release post; Wiz, PHP2Shell; PHP manual, expose_php; Cloudflare, full transaction detection.