AIREITER

OpenAI Codex PHP2Shell Checker Guide: Read UNKNOWN Correctly

Last Updated: 2026-10-22 09:08:25

OpenAI's php2shell-checker is useful for a first pass, but it does not prove that a server is safe. It compares a PHP version exposed by HTTP headers or php -v with embedded patch cutoffs, so a hidden header can produce UNKNOWN and a distribution backport can make a patched server look vulnerable.

OpenAI php2shell-checker GitHub repository

Start with the result states

The official OpenAI repository reports three meaningful states. Treat them as triage signals, not as a complete vulnerability assessment.

OutputExit codeWhat it tells youNext action
OK0A recognized branch is at or above its minimum fixed versionVerify that the PHP process serving web traffic uses that version
VULNERABLE1The detected version is below the minimum fixed versionPatch the serving runtime, restart its workers, and rerun both checks
UNKNOWN3The checker could not establish a recognized version or responseInspect the server locally; do not treat this as patched
Invalid arguments2The command itself was not validCorrect the command and run it again
PHP2Shell checker result codes

What OpenAI shipped

The public php2shell-checker repository contains a Node.js script, tests, and two Codex skills: phpcheck-url for a reachable site and phpcheck-server for a server where you have access. It inventories version evidence; it does not exploit a target or scan application code.

Run the check in the right mode

Check a public URL

Clone or download the repository, then run its Node.js script against a site:

node phpcheck.js "https://example.com"

The remote check sends an HTTP HEAD request and falls back to GET only when the server returns 405 or 501. It reads X-Powered-By and Server, does not follow redirects, and does not inspect the response body.

A CDN, reverse proxy, expose_php=off, or custom server configuration can remove the PHP version before the response reaches the checker. A VULNERABLE result needs patching; an absent header needs local evidence.

Check a local PHP binary

On a machine with PHP installed, use:

node phpcheck.js --local

This runs php -v on the binary available on the current PATH. It does not prove that the same binary serves your website: one host can have separate CLI, PHP-FPM, CGI, Apache module, container, or source-built installations.

Identify the runtime connected to the web server after the local check. In a containerized deployment, inspect the running container rather than only its image tag. For a packaged service, check the relevant PHP-FPM pool or Apache module and the vendor package changelog.

Install and use the Codex skills

The official README installs both skills with one Codex command:

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

Then choose the interface that matches your access:

$phpcheck-url check https://example.com
$phpcheck-server check the PHP version serving my site

Codex is optional. The Node.js command remains the direct interface for CI jobs and minimal hosts. For automation, add --json; its patch_status field returns ok, vulnerable, or unknown instead of requiring a script to parse terminal prose.

Verify the downloaded checker

The repository uses Node's built-in test runner:

node --test phpcheck.test.js

The suite exercises the CLI against local HTTP servers and uses a fake PHP executable for local scenarios. It requires neither a real PHP installation nor access to an external website, according to the README.

Read UNKNOWN as a debugging result

Treat UNKNOWN as incomplete evidence. The target may hide its PHP version, use an unrecognized branch, fail to respond, or run a newer prerelease; those outcomes do not establish that the fix is present.

A real user, @jvr0x, summarized the limitation this way:

“It's just a version compare against their Oct 20 cutoffs, so hidden headers come back UNKNOWN.”

The remote command sees an HTTP response, not the process behind the reverse proxy. A hidden header requires a server-side check rather than repeated URL probes.

Use this sequence after UNKNOWN:

  1. Run node phpcheck.js --local on the host serving the site.
  2. Identify whether the request reaches PHP-FPM, CGI, Apache mod_php, or a container.
  3. Check the vendor package changelog for backported patches.
  4. Restart the relevant PHP-FPM or Apache workers after updating.
  5. Verify through the serving path, not only the shell's default php binary.

Without SSH, URL mode may be the only first pass. If a shared host suppresses its version header, the hosting provider must confirm the serving runtime, package revision, and restart status.

The version table is not the whole patch decision

The checker treats PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 as the minimum fixed upstream releases for their branches. Its official tests confirm that 8.4.26 is VULNERABLE while 8.4.27 is OK; PHP identifies 8.5.12, 8.4.27, 8.3.36, and 8.2.35 as security releases.

BranchMinimum fixed upstream releasePrevious release classified vulnerable
PHP 8.58.5.128.5.11
PHP 8.48.4.278.4.26
PHP 8.38.3.368.3.35
PHP 8.28.2.358.2.34
Deployment situationWhy a simple version comparison can misleadEvidence to use
Hidden X-Powered-By or Server headerThe remote checker cannot identify PHPLocal runtime inventory
Debian or Ubuntu security packageA vendor may backport the fix under an older upstream version stringVendor advisory and package changelog
PHP-FPM and CLI on one hostphp -v may describe the CLI, not the HTTP workerWeb-server and FPM configuration
Cached container or pinned digestThe registry tag may be current while the running container remains oldRunning container image ID and version
Apache or FPM workers not restartedFixed files may be on disk while older workers remain activeProcess start time and post-restart check
Unsupported branch or prereleaseThe checker may not recognize vendor-specific patch statePHP or vendor security notice

The repository's limitations section says the tool assumes later stable patches in a listed branch contain the fix and cannot verify the fix independently. A distribution can apply a security backport without changing the upstream version number, making its changelog stronger evidence than the displayed number alone.

Updating WordPress, Drupal, Laravel, or another PHP application does not patch the PHP engine. Patch the runtime package or image, restart the workers, and verify the process that accepts web requests.

FAQ

Which output can close a production remediation ticket?

No output should close a ticket by itself. Pair OK with the serving SAPI, vendor backport status, all relevant vhosts or containers, and confirmation that workers restarted after patching.