AI Brief, 22 October 2026: the decade-old PHP flaws that needed a model to become exploits

PHP released 8.5.12, 8.4.27, 8.3.36 and 8.2.35 on 21 October, fixing two vulnerabilities that allow unauthenticated remote code execution in the engine itself, independent of any application running on top of it. The advisory's own position on mitigation is that "there are no complete mitigations, updating is the only safe choice", and the project issued minimal source backports for PHP 7.4, 8.0 and 8.1, versions it no longer supports. Wiz Research, which reported both, says every PHP release since 5.4 is affected by at least one of them.

This leads an AI brief rather than a security one because of the claim attached to it, and that claim needs separating from its headline. Wiz titled its write-up "AI uncovers critical RCE vulnerabilities affecting 70% of the Internet", but its body makes a narrower and more interesting argument: the flaws stem from architectural decisions made over a decade ago, and what frontier models changed was exploitability, not discovery. Building a working exploit meant chaining several flaws and adapting to each target's memory layout, which Wiz calls "at the edge of what human researchers could practically achieve". OpenAI's companion post attributes it differently again: it says Calif identified one of the flaws using GPT‑5.6 Sol, with Wiz independently discovering and reporting both. So there are two discovery tracks, one model-assisted, plus a separate claim that models made the exploit practical. None of it is independently audited, and all of it comes from parties with a commercial interest in the capability.

The response is not in dispute: two months of coordination across PHP's maintainers, Wiz, Calif, OpenAI and Cloudflare, plus a version checker as a Codex skill. OpenAI calls the pattern a "Defender's Window" and says that, to its knowledge, frontier AI found these flaws before attackers did. That hedge is load-bearing.

The same twenty-four hours supplied the counterexample, and nobody had to speculate about it. A malicious release of litellm, the LLM proxy most Python shops have somewhere in a requirements file, sat on PyPI for about two hours carrying a credential stealer whose target list reads like an inventory of this readership's tooling: Claude Code, Cursor, Codex, Aider, Zed, Hugging Face tokens, and the provider keys inside a LiteLLM proxy's own database. Offensive AI was the abstract half of the day; an implant harvesting AI developer credentials was the concrete half.

  • Two CVEs, severity reaching CVSS 9.2: CVE-2026-109618 (RCE via OOM corruption) and CVE-2026-109619 (RCE via stale global state). Risk is highest under mod_php, where an unauthenticated request triggers the attack before application code runs.
  • AI's role is attributed differently by the two write-ups: Wiz credits models mainly with making exploitation practical; OpenAI says a model identified one flaw outright.
  • litellm 1.107.0 was on PyPI from 11:39 to roughly 13:40 UTC on 21 October and has since been removed. Versions 1.106.2, 1.107.0rc1 and 1.107.0rc2 are clean.
  • The implant ran in every Python process on the host, including pip and IDE language servers, not only programs importing litellm.
  • Recursive published Flint, a 39.8B-total, 2.5B-active Apache-2.0 coding model, at 14:20 UTC. None of the thirteen organisations that ship weights regularly created a repository in the window.
  • Anthropic's counterweight, the same day: Claude over 25 projects surfaced 493 attack-surface issues, its own measurement.

Two memory bugs, and the part that is actually new

Both are lifecycle bugs rather than parsing bugs, which is why they survived so long. CVE-2026-109618 turns on out-of-memory handling: an allocation failure can interrupt PHP while a request's internal data is only partially initialised, and cleanup then treats the invalid data as valid. CVE-2026-109619 is the mirror image in time rather than space: request-owned state can outlive the memory backing it, and a later request on the same worker may use that stale state before initialisation resets it. Both reward an attacker who can make a worker fail at a chosen moment, which is why the partial mitigations are all about denying that control:

; CVE-2026-109618 — remove the controllable allocation-failure path. Set where
; an application cannot override it (php_admin_value in the FPM pool).
memory_limit = -1
post_max_size = 8M        ; 32M if uploads are required
upload_max_filesize = 8M  ; keep strictly below post_max_size

; CVE-2026-109619 — blocks the confirmed session path only, not other variants.
session.upload_progress.enabled = 0
fatal_error_backtraces = 0   ; PHP 8.5 only; the fatal-backtrace path

Every worker must be restarted for the second group to take effect, and ini_set() or an FPM .user.ini is not good enough.

The novel claim is about cost, not severity, and it is the one to hold loosely. Wiz argues that when complexity stops being a barrier to exploitation, the practical definition of "vulnerable" changes, and reaches for Heartbleed and Log4Shell as comparisons. The checkable parts are thinner: neither party has published an exploit, a model transcript, or any measurement of how much faster the work went, so "hours instead of weeks" is an assertion.

Anthropic published the opposite argument the same day, and it is the more falsifiable of the two. Its post by Chris Rohlf, dated 20 October, holds that fixing individual vulnerabilities scales badly, because fixing even a critical bug tells you nothing about what remains. The alternative is automated attack-surface reduction: use a model to map which code is actually reachable in a deployment, then remove or restrict whatever that deployment does not need to expose. The reported result is 493 issues across 25 projects, Anthropic's own count with no external replication, and "issues" is doing some work in it. But its underlying claim, that cutting reachability lowers the severity of vulnerabilities nobody has found yet, is what PHP2Shell illustrates from the other side.

A two-hour window on PyPI, aimed squarely at coding agents

SafeDep's Kunal Singh published the analysis on 21 October, and the mechanism generalises to every Python package you install.

The only visible change in the source distribution of litellm 1.107.0 was one line in the maturin build section of pyproject.toml: data = "cook". That copies a cook/ folder into litellm-1.107.0.data/purelib/ inside the built wheel, and pip installs purelib straight into site-packages. The sdist omitted the cook/ folder, so there was nothing else to notice, and no public commit in BerriAI/litellm contains that line or that folder.

pyproject.toml data = "cook" maturin build wheel .data/purelib/ pip install site-packages Python runs any import line in a .pth file at startup cook.py credential stealer sitecustomize.py hourly remote-code loop
How one build-configuration line reached every Python process on an affected host. The top row is the build path; both payloads at the bottom run at interpreter start.

The delivery trick is the detail to remember. Python executes any line beginning with import in a .pth file in site-packages, at interpreter startup, before anything imports anything. cook.pth contained import cook, so the stealer ran in every Python process on the host — the application, pip itself, IDE language servers, CI jobs — including processes that never touch litellm. sitecustomize.py, which Python also imports at startup, forked a detached daemon that hourly re-read a server address from a GitHub file, ran the stealer again, and exec()'d whatever that server returned.

What it collected is why this is an AI story. Alongside SSH keys, cloud credentials, browser passwords and CI secrets, one module targets AI developer tooling specifically: Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code mcp.json, Gemini CLI, Codex auth.json, Continue, Aider, Goose, opencode, Qwen, Crush, Kiro, Zed and the Hugging Face token. Another reads DATABASE_URL and dumps LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable, which hold a LiteLLM proxy's provider API keys. Results went out encrypted to a typosquat of the project's own domain.

The forensics point at the release pipeline rather than the index. The tag v1.107.0 points at a commit whose pyproject.toml reads version = "1.108.0"; the published source matches a later main commit plus five edited files and the cook/ folder. The version edit set the number back to 1.107.0 and pinned the companion packages to the release-candidate values, so a dependency diff against rc2 showed nothing unusual. The wheels otherwise match the project's normal pipeline, and no public GitHub Actions run built the release. SafeDep's assessment, at moderate to high confidence, is that BerriAI's private pipeline built these wheels from an attacker-modified source: an inference, labelled as one, and the entry vector is still unknown.

Three things are verifiable without taking SafeDep's word. The malicious version is gone from the index: litellm lists 948 versions, the newest 1.106.2 uploaded at 10:44 UTC on 21 October, and no 1.107.0. PyPI holds no provenance attestations for 1.107.0, 1.106.2 or rc2, so attestation would not have helped a careful installer. And the attacker's GitHub account disappeared two hours after upload, leaving the hourly daemon with no address to resolve: dormant on any host that still has it, not removed.

# None of these three files should exist.
python -c "import site,os,glob;[print(p) for d in site.getsitepackages() \
  for p in glob.glob(os.path.join(d,'cook.pth'))+glob.glob(os.path.join(d,'cook.py')) \
  +glob.glob(os.path.join(d,'sitecustomize.py'))]"

# Did the stealer upload, or the daemon run?
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null

Anyone who finds those files should treat every credential reachable from that host as disclosed, rotate rather than audit, and pin to 1.106.2. This is the second provider-key incident in five days: the OpenCode console breach covered here on the 18th still has no postmortem, and the stealer lists opencode among its targets by name.

Flint: 40B total, 2.5B active, and a superlative with two qualifiers

The window's only significant open-weights release came from a company four months into building models. Recursive published RecursiveSI/flint at 14:20:58 UTC on 21 October under Apache 2.0, with the announcement five hours later.

The architecture is the interesting part, and most of it is in config.json rather than the card. It is a mixture-of-experts decoder with 39,785,287,456 parameters in BF16 and roughly 2.5B active per token, across 49 layers: two dense, then 47 MoE layers with 256 routed experts of which six fire per token, plus an always-active shared expert. The experts are unusually narrow, an intermediate size of 512 against a hidden size of 2,048. Attention is hybrid in two directions at once: 35 of the 49 layers use a 513-token sliding window and 14 use full attention, and the full layers carry no positional encoding at all, while RoPE is applied only on the windowed ones.

That split is what makes the long-context arithmetic work. With nkv=4 key/value heads of dimension dh=128 , each layer stores 2nkvdh=1,024 values per token, or 2 KiB in BF16. At the documented 262,144-token context the 14 full-attention layers hold

14×262,144×2 KiB=7.0 GiB

while the 35 windowed layers, capped at 513 tokens each, contribute about 35 MiB between them. Had all 49 layers kept a growing cache the same context would need 24.5 GiB against roughly 7 GiB here, a 3.5-fold reduction with all the remaining cost in 14 layers.

Recursive reports 54.9% on Terminal-Bench 2.1, 89.7% on SWE-bench Pro v2 and 74.2% on SWE-bench Verified, all its own measurements, at temperature 1.0, top-p 0.95, top-k 20, averaged over at least five attempts. The claim wrapped around them carries two qualifiers: Flint "leads MoE models with fewer than 50 billion total parameters" on agentic coding. Remove either and the sentence stops being supported by anything published. This is the construction flagged here against Meta's "most capable US open-weight model" on the 16th, and the comparison chart is again a raster image, making it the fourth launch this month whose benchmark table ships as a picture, after the two Meta posts on the 13th and 16th and Timestral on the 19th.

Credit where the card earns it: Recursive discloses that its Terminal-Bench figure counts solutions obtained through web retrieval as unsuccessful, and notes that developer-reported comparison scores may not apply that adjustment. It is a self-penalising note most launches omit.

Two caveats sit on the local-inference story, which is the model's whole pitch. The published checkpoint is BF16, so the weights alone are 79.6 GB — more than the 64 GB device the announcement quotes 150 tokens per second on. The speed figures therefore describe quantised builds distributed separately, not this repository, and neither has been independently measured. Transformers modelling code is also absent; serving is via an SGLang kit only.

On attention the release is loud in one place and quiet in another: 293 likes on Hugging Face within a day and at least 17 community conversions from a dozen accounts — GGUF, NVFP4, FP8, int4 and MLX builds, the newest at 04:22 UTC this morning — against 22 points on Hacker News for its own announcement. Unsloth's GGUF conversion is stamped 2026-10-20T03:26 UTC, a day and a half before the original repository existed publicly, which says the quantisers knew first.

Also notable

  • Anthropic opened a research preview of Claude Duet on 21 October, the window's loudest product launch at 336 points on Hacker News. A preview, not general availability, with no measured claim to check.
  • OpenAI models are coming to Google Cloud, announced 21 October at 260 points. A frontier lab distributing through a competitor's cloud is structural rather than a feature; the published terms name neither models nor regions.
  • Meta's Muse exposed personal and business details through shared conversations, per Ars Technica on 21 October. This is the assistant product, not the Muse Spark Open weights covered here on the 16th.
  • Reuters, citing the Wall Street Journal, reports a net loss above $80 billion for Anthropic in the first half of 2026. The originating report is paywalled, so the figure is secondhand, and nothing published separates a cash operating loss from a remeasurement.
  • Google staff have resigned over its military AI contracts, per the BBC, 21 October.

What to watch

  • Whether BerriAI publishes a postmortem naming the entry vector. SafeDep's conclusion that the project's own release pipeline built the malicious wheels is an inference, and only the maintainers can make it a fact. The adjacent question is whether this moves PyPI provenance attestations from optional to expected, given none existed for 1.107.0 or 1.106.2.
  • Whether anyone measures a Flint number on stated hardware. The weights are Apache 2.0 and the sampling configuration is published, so 150 tokens per second on a 64 GB device is checkable — and since the BF16 checkpoint does not fit on it, the first independent figure also establishes which quantisation the claim describes.
  • Whether any exploit, transcript or timing measurement is published for PHP2Shell. The capability claim rests on two vendor accounts that attribute the model's contribution differently; one measurement of how much faster the work went would move it from assertion to evidence.
  • MiniMax M3.1 Flash weights have still not appeared, flagged here on the 20th and 21st against a press-reported 23 October date: no repository matching M3.1 exists anywhere on Hugging Face, under MiniMaxAI or any other account. Voxtral 3 weights are also still absent, flagged on the 21st — every Voxtral repository published since is a mirror of the older 2507 generation. And no decision-model vendor has published its own fail-open rate, asked daily since 10 October, now thirteen.

Daily, by email

Stay current on AI without the scrolling

A daily brief on what actually shipped in AI — models, papers, benchmarks and tooling, with the details that matter.

Confirmation email first, one message a day, unsubscribe in one click.