Short version: between 14 and 21 October 2026, PHP, Node.js and nginx each shipped security fixes that matter to anyone who sells or buys through x402 or MCP. PHP2Shell lets an anonymous request run code on a PHP server, and attacks began before the patch was out. Node.js fixed a bug in its Brotli decoder that can turn a compressed body into code execution in whatever process decodes it, including an agent reading a response, plus two ways around its TLS hostname check. nginx fixed three memory bugs that F5 rates critical in specific configurations. A 402 paywall protects you from none of them: the payment check runs in your application, and all of these bugs are reached before a request gets there. Fixed versions and a checklist are below.
The four fixes at a glance
| Stack | Issue | CVEs | Fixed in | Released |
|---|---|---|---|---|
| PHP | PHP2Shell: unauthenticated code execution, CVSS 9.2 each | CVE-2026-109618, CVE-2026-109619 | 8.5.12, 8.4.27, 8.3.36, 8.2.35 | 21 Oct |
| Node.js | Brotli decoder out-of-bounds write, rated high | CVE-2026-111497 | 26.12.1, 24.22.1, 22.23.4 | 21 Oct |
| Node.js | TLS hostname check bypassed with unusual IP forms, rated high | CVE-2026-106069, CVE-2026-105483 | 26.12.1, 24.22.1, 22.23.4 | 21 Oct |
| nginx | Heap overflows and a use-after-free; critical per F5, medium per nginx.org | CVE-2026-15176, CVE-2026-93479, CVE-2026-97205 | 1.30.6, 1.31.7 | 14 Oct |
The nginx fixes are a week older, but security agencies were still issuing alerts about them this week (CSA Singapore, 22 October), and distribution packages lag behind nginx.org.
Why a paywall doesn't protect you
An x402 endpoint is public by design. Any client can send it a request without paying and get a 402 challenge back; that's how an agent learns the price. The code that decides between 402 and 200 is middleware in your application: paymentMiddleware from @x402/express or @x402/hono, or a PHP package such as laravel-x402. A paid MCP tool is the same, with the check inside the tool call (how that works).
Before that middleware sees a request, the request has been through your reverse proxy, your runtime's request setup and usually a body parser. That's where this week's bugs are:
- nginx processes the request before your application gets it.
- PHP parses multipart bodies and session upload progress while it sets the request up. The demonstrated mod_php exploit takes one unauthenticated request and runs before any application code (CERT-EU).
- Node.js decodes a Brotli-compressed body wherever something decompresses it. Express 5's
express.json()decompresses Brotli request bodies by default, so an app that mounts it ahead of the payment middleware decodes the body before the payment check.
None of this is specific to x402; any anonymous endpoint has the same exposure. What is specific is how easy the endpoints are to find. The x402 Bazaar and directories like ToolVet publish every URL so that agents can find them: on 18 October ToolVet listed 51,684 paid x402 APIs and 28,955 remote MCP servers (data). The lists that bring you buyers also save anyone scanning for unpatched servers the trouble of looking. That's a reason to patch on the day, not to stay unlisted.
PHP2Shell: one request, before your code runs
PHP2Shell is two flaws in the PHP runtime, both rated 9.2 under CVSS v4:
- CVE-2026-109618: PHP stops a request that exceeds
memory_limitby jumping out of whatever it was doing. If that happens while it's parsing a file upload, the cleanup can free memory that was never set up and may hold the attacker's data. - CVE-2026-109619: state from one request outlives its memory, and a later request on the same worker uses it. Session upload progress, which is on by default, is one confirmed way in.
Every way of serving PHP over the web is at risk: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. mod_php is the worst case. On PHP-FPM, full code execution has been shown only for CVE-2026-109619, and only with help from something the application does. Nearly every version back to PHP 5.4 is affected; PHP 8.1 and older get no official release, though the PHP security team published source backports for 7.4, 8.0 and 8.1.
Even without code execution, researchers could read a live worker's memory, which can hold other requests' passwords, tokens and keys (The Hacker News). The timeline was rushed: a video of a working proof of concept leaked on 20 October, PHP brought its release forward to 21 October, Cloudflare reported early attacks before the patch shipped, and watchTowr reported active exploitation attempts the same afternoon. No confirmed breaches had been reported at the time of that report.
What it means for x402 and MCP sellers. PHP runs x402 APIs through packages like the one above and MCP servers through laravel/mcp, with add-ons that put tools behind x402 payments. A seller's PHP workers hold the secrets an attacker wants: the facilitator API key that settles payments, the upstream API keys the service resells, and the OAuth tokens of MCP users. And whoever controls your server controls your 402 challenge. Change the payTo address in it and every agent that pays you pays them instead, with nothing failing on your side. ToolVet records "Payment address changed recently" on a listing, puts the change in the listing's status feed, and its preflight check warns an agent when the address in a live challenge differs from the one we last saw.
What to do:
- Upgrade, then restart every PHP worker and confirm the version the web server runs, not only the CLI's. Distribution packages often backport the fix without changing the version number, so look for the CVE in the package changelog.
- If you can't upgrade today, the interim settings reduce exposure without fixing it:
session.upload_progress.enabled=0,memory_limit=-1set withphp_admin_valueand paired with a container or cgroup memory limit, a lowpost_max_size, and on PHP 8.5fatal_error_backtraces=0. Set them inphp.inior the FPM pool, not withini_set()or.user.ini, and restart the workers. - Most x402 APIs and MCP endpoints only accept JSON. If yours is one of them, refuse multipart bodies before they reach PHP:
# An API or MCP endpoint that only takes JSON: multipart bodies never reach PHP.
location /api/ {
if ($content_type ~* "^multipart/") { return 415; }
# ...your existing fastcgi_pass or proxy_pass
}
- If a PHP server was reachable and unpatched from 20 October on, check it before you trust it: bursts of "Allowed memory size exhausted" errors, workers crashing, large multipart POSTs, PHP files in the web root you didn't put there. If you find any, rotate every secret the application can read, starting with facilitator and wallet credentials. The NCSC-NL php2shell repository helps find products that embed PHP without saying so.
The fix also adds a hard memory limit that ends the worker when it's reached, so review memory_limit and post_max_size for endpoints that handle large inputs.
Node.js: the response can attack the client
The Brotli bug (CVE-2026-111497). Malformed Brotli input makes the decoder bundled with Node.js write past its buffer, and Node.js says this can allow remote code execution. A process is exposed when it decompresses Brotli data it didn't produce: a request body, or a response from another server.
That second case covers almost every agent written in Node.js. Node's fetch() asks for Brotli on every HTTPS request that doesn't set its own Accept-Encoding, and it decodes whatever the response's Content-Encoding header names, so setting Accept-Encoding: identity doesn't protect you from a server that sends Brotli anyway. @x402/fetch wraps fetch(), and the MCP TypeScript SDK's Streamable HTTP client uses it by default. An agent that calls a paid API decodes that API's responses, starting with the unpaid 402, and an MCP client decodes everything the server streams back.
So for once the server can attack the client, and the client is often the process that holds the wallet key it signs payments with. A malicious listing wouldn't need to fool anyone into paying; answering the first request would do.
How close is that to practice? The proof of concept published on 22 October reached code execution only against a deliberately weakened build. On a standard build its author got a crash and a limited read of process memory, and no exploitation in the wild had been reported as of that day (The Hacker News). The fix is public, though, and it shows exactly where the bug is. Firewalls don't help on this side: Vercel added rules that block the attack in incoming requests, but they don't cover responses your code fetches, and only the runtime patch does (Vercel). The same is true of any WAF or CDN.
The TLS hostname checks (CVE-2026-106069, CVE-2026-105483). Node's certificate hostname check could be bypassed when a host is an IP address written in an unusual form, for example with fullwidth digits, so a certificate could be accepted for a host it doesn't cover. The attacker still has to be able to intercept the connection. It matters most to code that connects to hosts named by untrusted input, and agents do that all the time: resource URLs in payment challenges, links in tool results, URLs a model wrote.
DNS names with a null byte (CVE-2026-105482). dns.resolve cut names short at a NUL character. Many x402 APIs and MCP tools fetch URLs on a caller's behalf, such as scrapers, screenshot and crawl services, and guard against requests to internal addresses. A guard that checks a name as a string and then resolves it can approve one host while the lookup goes to another. Reject hostnames with control characters before any other check.
The release fixes six more issues, updates the bundled undici, the HTTP client behind fetch(), for three moderate advisories, and blocks eight eMudhra intermediate CA keys that were reported compromised days earlier.
What to do:
- Upgrade to 26.12.1, 24.22.1 or 22.23.4 everywhere Node.js runs: servers, agent hosts, CI runners, developer laptops. Node.js 20 is end of life and gets no fix.
- Rebuild container images with a fresh base image. A tag like
node:22-slimonly picks up the patch when you build with--pull; an image built last week still has the old runtime. - Until a server is patched, an Express app that doesn't need compressed request bodies can refuse them. This doesn't cover responses your server fetches:
// Until Node.js is patched: answer compressed request bodies with 415 instead of decoding them.
app.use(express.json({ inflate: false }));
nginx: three memory bugs, in specific configurations
On 14 October nginx released 1.30.6 (stable) and 1.31.7 (mainline) with five fixes, and F5 published an out-of-band advisory covering eleven issues across its NGINX products. F5 rates three of them critical, at 9.2 under CVSS v4 and 8.1 under CVSS 3.1 with high attack complexity; nginx.org rates all five medium. The difference is mostly about configuration: each bug needs directives that a lot of servers don't use.
| CVE | Reached when | Vulnerable versions | Interim mitigation |
|---|---|---|---|
| CVE-2026-15176 | alias serves a URI changed by rewrite, and $request_filename is used in set, return or a log format |
0.1.29 to 1.31.6 | Avoid alias in that location, or $request_filename |
| CVE-2026-93479 | A map's regex captures are used before its output variable, in root or a log format |
1.30.0 to 1.30.5, 1.31.2 to 1.31.6 | Use named captures, only in the block that matches |
| CVE-2026-97205 | Asynchronous file I/O (aio) with SSI pages that use include virtual |
0.8.11 to 1.31.6 | Turn off aio or SSI |
An unauthenticated request crafted for one of these configurations crashes a worker, and can run code where ASLR is off or can be bypassed. No exploitation had been reported at disclosure. A proxy that only uses proxy_pass to reach your app doesn't hit any of them, but shared configs and hosting panels can carry alias, map and SSI blocks that nobody remembers adding, so check:
nginx -v # 1.30.6 or 1.31.7, or your distribution's backport
nginx -T 2>/dev/null | grep -nE '\b(alias|map|aio|ssi)\b|\$request_filename'
Two of the medium-rated fixes are worth a look if they match your setup. CVE-2026-97629 lets an attacker forge TLS sessions where ssl_session_cache is shared and no ssl_session_ticket_key is set, which can get around client-certificate checks: relevant if you use mutual TLS between services, for example to a self-hosted facilitator. CVE-2026-95101 lets HTTP/3 requests inject data into proxied request bodies when proxy_request_buffering is on. If you run NGINX Ingress Controller or NGINX Gateway Fabric, F5's advisories list their fixed versions separately.
Checklist for this week
If you sell an x402 API or run an MCP server:
- Find out what each server actually runs: the PHP the web server uses,
node -vinside the container,nginx -von the proxy. - Upgrade and restart. Rebuild images with
--pull. - Where you can't upgrade today, apply the stopgaps above: refuse multipart at the edge for JSON-only PHP endpoints, set the PHP interim settings, and turn off body inflation in Express.
- If a PHP server was exposed and unpatched from 20 October on, look for signs of compromise and rotate the secrets it can read.
- Check that your 402 challenge still pays you: the free x402 checker shows the
payTo, amount and token an agent sees.
If you run agents or MCP clients:
- Upgrade Node.js wherever agent code runs, including CI and serverless functions you deploy yourself.
- Keep the signing key out of the process that fetches untrusted content. A separate signer, or a wallet service that signs on request within a budget, means code execution in the fetching process doesn't come with the key.
- Fund hot wallets for days of spending, not months.
- Before paying a URL, check it: ToolVet's preflight check flags a
payTothat differs from the one we last saw.
Frequently asked questions
Does an x402 paywall protect my API from PHP2Shell?
No. The 402 response comes from your application's code, and the demonstrated PHP2Shell attack runs while PHP is still setting up the request, before any application code. Every endpoint that accepts anonymous requests is exposed, paid or not.
Is my MCP server affected by the Node.js Brotli bug?
If it runs Node.js older than 22.23.4, 24.22.1 or 26.12.1 and either decompresses request bodies or fetches from other servers with fetch(), yes. Express 5's JSON parser decompresses Brotli bodies by default, and fetch() decodes Brotli responses automatically.
Can an x402 API attack the agent that pays it?
In principle, through the Brotli bug: the agent decodes the API's responses, including the unpaid 402. The public proof of concept reached code execution only on a weakened build, and no attacks had been reported as of 22 October. Upgrading Node.js closes it.
Does a CDN or web application firewall protect me?
Partly, and only for incoming requests. Firewall rules can filter malformed bodies on the way in, but they don't see responses your code fetches from other servers, and attackers changed their PHP2Shell payloads to get around early Cloudflare rules. Every vendor involved says to patch.
Does ToolVet's trust score show whether a listing is patched?
No. Our checks see what an agent sees: the 402 challenge, uptime, latency and tool definitions. They don't read software versions. What they do catch is the most profitable thing to do with a compromised seller: changing the payment address, which shows as a finding on the listing and in its status feed.
Found something in this post that's wrong or out of date? Write to [email protected]. Corrections go in the changelog.