---
title: "Black October: A Security Filter, Switched Off by a Name"
description: "Finding the bug took a day. Writing the fix took an hour. The handoffs took two weeks. How a policy bypass in vLLM Semantic Router's Istio integration works, how to check for it, and what it would take to fix faster."
author: "Colin McNamara"
tags: ["Security","Open Source","AI","Inference"]
image: "https://colinmcnamara.com/images/blog/black-october/talk-video.jpg"
date: 2026-11-09
url: https://colinmcnamara.com/blog/black-october
---

# Black October: A Security Filter, Switched Off by a Name

A request that should have been blocked came back 200.

No error. Not a line in the guard's log. The most dangerous response is the one that looks fine.

This is the long version of my November 2 talk at [AIMUG](https://aimug.org) in Austin. I had a 22-minute slot, so a lot got cut. This version has all of it. How a token bill led me to a security advisory. How the bug works, and how to check for it. What coordinated disclosure really took. And what I think it would take to do it faster. Watch the talk, or [click through the slides](https://colinmcnamara.com/talks/black-october).

[![Black October: a security filter switched off by a name. Colin McNamara at AIMUG, November 2026. Watch on YouTube.](https://colinmcnamara.com/images/blog/black-october/talk-video.jpg)](https://www.youtube.com/watch?v=8_1kXuBmww4)

*[Watch the talk on YouTube](https://www.youtube.com/watch?v=8_1kXuBmww4) (41 minutes with Q&A).*

**The short version.** One routing header let a request skip the policy guard in vLLM Semantic Router's Istio integration, because two filters shared a name. The fix is a rename, and I sent it with the report; the advisory was public six days later. Getting the lesson to the other projects I found running the same pattern took two weeks. Run Istio 1.27 or later with an InferencePool route and a third-party ext_proc filter? [Check yours](#name-your-filters-like-you-own-them).

## Black October

At November's AIMUG meetup, I asked who had patched PHP since October 21. Nearly the whole room raised a hand.

A widely shared post on October 22 used the name Black October. It listed a long run of incidents and called them the most serious 48 hours in cybersecurity history. That claim doesn't survive a close look. Several of those attacks had been running for months and only became public that week. Several came down to old-fashioned failures: a leaked key that led to a hijacked release pipeline, exposed credentials, a certificate authority with weak keys. It was a disclosure wave, not one campaign.

The centerpiece was real, though. On October 21, [PHP shipped fixes](https://www.php.net/archive/2026.php#2026-10-21-5) for two critical bugs, CVE-2026-109618 and CVE-2026-109619, that allow unauthenticated remote code execution. People call it PHP2Shell. Three teams found it separately, Calif, OpenAI and Wiz, all with AI in the loop.

[Amazon saw an exploitation attempt](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/) on October 17, four days before the fix shipped. CISA added both bugs to its [Known Exploited Vulnerabilities catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) on October 26. And [PHP's own foundation says](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/) early signs of the out-of-memory bug behind one of the two CVEs surfaced during follow-up to a 2024 audit; the fix stalled on hard tradeoffs, uncertain exploitability and limited resources.

It also wasn't the end of the internet. Within a week, [Wiz saw about two thirds fewer](https://www.wiz.io/blog/php2shell-exploitation-so-far) exposed, vulnerable machines in its telemetry, and no complete exploit land there. Honeypot researchers did [catch working shells](https://github.com/0x4D31/honeypot-data/blob/main/reports/php2shell/2026-10-24-confirmed-rce.md) on deliberately easy targets. Wiz reads that as people patching, upgrading, retiring machines and locking down access. That took a lot of people dropping what they were doing.

If you run PHP and haven't patched, start there. Install the fixed release and restart whatever serves it. Look for suspicious activity at least back to October 17. Then read PHP's [October 23 follow-up advisory](https://www.php.net/archive/2026.php#2026-10-23-1): it covers one more, rarer vector, CVE-2026-115560. If your application switches its session serialize handler at runtime, turn off session upload progress until the next release.

That's the real story. Finding the bugs wasn't the hard part. Everything after was.

### Finding got cheap

Finding bugs got a lot easier this year, for everyone:

- Mozilla [fixed 271 bugs in Firefox 150](https://hacks.mozilla.org/2026/05/behind-the-scenes-hardening-firefox/) that it found with Anthropic's Mythos Preview.
- Google's Big Sleep found [45 actionable V8 bugs in six months](https://projectzero.google/2026/10/fuzzing-v-AI.html), against 37 from its Fuzzilli fuzzer. Google's conclusion: run both.
- In [Anthropic's own study](https://www.anthropic.com/research/n-days), agents turned 8 of 18 patches to Firefox's JavaScript engine into working exploits, the first in under an hour. Ship a patch, and the diff is the map.

![Published CVE records by quarter. 2025 runs flat: 12,009, 11,701, 11,738 and 12,796, with Q1 to Q3 combined at 35,448. 2026 climbs: 15,154, 20,696, then 36,962 in July to September alone, more than the first nine months of 2025 combined. The fourth quarter of 2026 is not in yet.](https://colinmcnamara.com/images/blog/black-october/cve-by-quarter.webp)

In July through September 2026, the CVE Program [published 36,962 records](https://www.cve.org/About/Metrics), more than in the first nine months of 2025 combined. These are published records, not discovery dates.

CISA's catalog of known-exploited bugs grew much more slowly: 276 additions between January 1 and October 27, against 210 over the same stretch last year. That's up about a third, while published records more than doubled. The catalog isn't a census of exploitation, and those counts don't tell us how long we have. But most newly published bugs have no confirmed exploitation yet. That's the window: there's still time to fix them, if anyone can get to them.

Their queue is your queue. As of October 26, [curl handles low and medium severity reports in public issues](https://daniel.haxx.se/blog/2026/10/20/do-minor-curl-vulnerabilities-in-the-open/), because the private queue can't keep up. Not because of AI slop, which [has mostly disappeared](https://daniel.haxx.se/blog/2026/10/22/ignore-the-slop/), but because the valid reports keep coming.

Finding scales with compute. Fixing doesn't.

## A token bill, a router and eight fixes

I'm not a security person. In 27 years of doing this work I had never found a zero-day. Then I stumbled into one.

At work, I was near the top of the Claude Code bill. I wanted to get more efficient, and that sent me looking for a router: one place to decide which model answers. I put my coding agents behind [vLLM Semantic Router](https://github.com/vllm-project/semantic-router), pointed at models on my own lab. OpenCode and Codex worked through it on day one. Claude Code didn't. The router didn't fully support Anthropic's Messages API yet: it tripped on a request field it didn't know, and some replies came back empty.

![My coding agents, Claude Code, OpenCode and Codex, send requests through vLLM Semantic Router, one control point, to models on my own lab. Eight merged fixes sit under the router: vLLM #55325, Semantic Router #4266, #4391, #4539, #4658, #4901 and #5150, and Switchyard #884. Claude Code's Anthropic API mostly works now. Above the router, the Data Plane and Networking working group, member since October 9.](https://colinmcnamara.com/images/blog/black-october/router-path.webp)

So I started fixing it. Instead of filing a request and waiting, I could write the code and send it upstream. vLLM first, in September, then the router itself: stop sequences, reasoning that streamed before the answer, error frames that got split in the stream. Eight fixes, all merged. One in vLLM ([#55325](https://github.com/vllm-project/vllm/pull/55325); the story is in [The Router Was Right](https://colinmcnamara.com/blog/the-router-was-right)). Six in the router ([#4266](https://github.com/vllm-project/semantic-router/pull/4266), [#4391](https://github.com/vllm-project/semantic-router/pull/4391), [#4539](https://github.com/vllm-project/semantic-router/pull/4539), [#4658](https://github.com/vllm-project/semantic-router/pull/4658), [#4901](https://github.com/vllm-project/semantic-router/pull/4901) and [#5150](https://github.com/vllm-project/semantic-router/pull/5150)). One in NVIDIA's Switchyard ([#884](https://github.com/NVIDIA-NeMo/Switchyard/pull/884)). Claude Code mostly works through the router now.

Somewhere in there it became the most fun I'd had in years. I applied to the router's Data Plane and Networking working group on September 24 and became a member on October 9. Now I get to do the work alongside people who are way smarter than me.

On October 5, at Mixture of Models here in Austin, I made a promise about routers. Most teams start with one router for one app. Then a second team copies it, a third forgets the guard, and now your privacy policy has three versions. Put the policy in one place instead: "One policy, enforced once, for every app." Cost, privacy and security at one control point.

Five days later, building a lab to show that promise off, I found one that wasn't a bug in my client.

## One header, and the guard is skipped

It was October 10, around 3 in the morning. I was burning down bugs with AI in the loop, but I was also reading the code, because I wanted to understand how routing works across these projects. That meant learning Go. The AI called what I'd found a bug. I read the code again, and the bug turned into something else. Holy smoke: I could skip policy by setting a header.

The [advisory is public](https://github.com/vllm-project/semantic-router/security/advisories/GHSA-xr66-pq9r-2c8j), so I can walk through it. Two roles matter. The guard enforces your policy: it's the service that says no to a prompt injection, or to someone pasting a Social Security number into an agent. The endpoint picker chooses which model server gets the request.

Here's the chain. A request enters the Istio gateway. The router runs as an ext_proc filter and calls the guard service over gRPC. Then Istio's own ext_proc filter calls the endpoint picker to choose a pod. Then Envoy's router sends the request on. Same filter type, and, it turned out, the same name.

![The Istio gateway's filter chain. Without a routing header, the router's filter asks the guard, and the guard blocks the request with 403. With one routing header, the request lands on an InferencePool route. Istio's per-route settings, filed under the shared filter name, reach the router's filter too, so its call goes to the endpoint picker. The guard is never called, and the request is served with 200.](https://colinmcnamara.com/images/blog/black-october/one-header.webp)

*[Watch this part](https://www.youtube.com/watch?v=8_1kXuBmww4&t=369s) (6:09) · [Step through it in the deck](https://colinmcnamara.com/talks/black-october#/header)*

1. **Two filters share one name.** The router's filter and Istio's endpoint picker filter were both registered as `envoy.filters.http.ext_proc`.
2. **Istio files its per-route settings under that name.** When a request lands on an InferencePool route, Istio attaches settings for the filter with that name. Envoy looks them up by name, not by owner, so they reach both filters.
3. **So your filter's call goes to the endpoint picker, and the guard is never called.**

Any client can set the routing header that puts a request on that route. And if a catch-all rule already sends traffic to the pool, no header is needed at all.

> Per-route config keys are addresses, not labels.

I recorded it in a lab with a neutral stub guard, on Istio 1.28.0, on October 18:

| Request | Route | Result |
|---|---|---|
| Marked (should be blocked) | Route without a pool | 403, blocked |
| Clean | InferencePool route | 200, inference served |
| Marked (should be blocked) | InferencePool route | 200, no error, no line in the guard's log |
| *Rename the filter, then:* | | |
| Marked (should be blocked) | InferencePool route | 403, blocked again |
| Clean | InferencePool route | 200, inference still served |

Look at the second row. That clean request had already skipped the guard. It just didn't matter, because the request was clean.

There's a second effect. On other backends in a rule that includes a pool, Istio writes a "disabled" setting under the same name, and your filter is switched off. That came with a September change that resolves endpoint pickers per backend ([istio/istio#61601](https://github.com/istio/istio/pull/61601)), backported to 1.29.9, 1.30.6 and 1.31.2. The change is sound. It just widened what the collision does.

Your guard was configured, deployed and healthy. The route told it to call someone else. Anything you put behind that filter, from content policy to monitoring, gets skipped the same way.

One policy, enforced once, only counts if the request reaches it.

## How it got in

Nobody wrote a bug.

![How it got in. March and April 2025: the Gateway API Inference Extension's body-based routing chart, then Semantic Router in plain Envoy, use the bare filter name, harmless then. July 2025: Istio starts filing per-route settings by filter name, shipping in 1.27. November 2025: Semantic Router's Istio setup lets a client header pick the InferencePool, reachable from here. March 2026: the chart renames its filter as a routine fix, with no channel to share the lesson. October 10, 2026: found.](https://colinmcnamara.com/images/blog/black-october/how-it-got-in.webp)

In March 2025, the Gateway API Inference Extension's body-based routing chart used the bare filter name, and in April the router did the same in plain Envoy. Harmless then: nothing else in the chain wrote settings under that name. In July, Istio started filing per-route settings by filter name, shipping in 1.27. That fall, the router's Istio guide used the bare name, and a client header could pick the pool. That's when it became reachable, by my reading of the public history.

In March 2026, the chart renamed its filter to `envoy.filters.http.ext_proc.bbr` as a routine bug fix ([#2643](https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/2643), merged March 20, shipped in v1.5.0). Nothing carried that lesson to the other integrations using the name: dependency tooling doesn't track a shared filter name, and that's a gap in all of our processes. In September, the Istio backports added the switched-off effect. On October 10, I found it.

It takes three ingredients:

1. **Flat filter names.** Every filter in the chain shares one namespace.
2. **A control plane that writes per-route settings by name.**
3. **An insertion path that doesn't check** whether the name is already taken.

In weakness terms, duplicate identifiers ([CWE-694](https://cwe.mitre.org/data/definitions/694.html)) are the cause. When the filter is a security control, the consequence is a protection mechanism failure ([CWE-693](https://cwe.mitre.org/data/definitions/693.html)).

Istio uses that name for the filter it generates, so the fix belongs on the filter side: give yours a distinct name. People ask why Istio doesn't just rename its own filter. That would remove the collision, but EnvoyFilters that match Istio's filter by name today could change behavior, so I think it's a separate question. I laid out that reasoning in the public issue, [istio/istio#62556](https://github.com/istio/istio/issues/62556).

Every step made sense where it was made. Flat names, settings keyed by name, and no shared check for collisions: that's a class of bug, not anyone's mistake.

## Name your filters like you own them

Checking takes one command, and the fix is a rename. The setup I reproduced had three things on one gateway: Istio 1.27 or later, an InferencePool route, and a third-party ext_proc filter under the bare name. If you run anything like it, check.

Dump the gateway's config and look for the name twice:

```bash
istioctl proxy-config all <gateway-pod> -o json
```

- Two HTTP filters named `envoy.filters.http.ext_proc` in one HTTP connection manager.
- A per-route entry under that name that points at the endpoint picker, or sets `disabled: true`.
- The fastest tell: the endpoint picker logs every request twice.

The rename is small: same filter type, its own instance name.

```diff
- name: envoy.filters.http.ext_proc
+ name: vllm-semantic-router.ext_proc
```

![The same chain after the rename. The router's filter is now named vllm-semantic-router.ext_proc. The per-route settings reach only Istio's InferencePool filter, and the router's filter asks the guard again: 403.](https://colinmcnamara.com/images/blog/black-october/after-rename.webp)

Validating it and rolling it out is the work. Move every reference with it: per-route keys, patch matches, charts and tests. Then check the config the gateway actually received, and send the marked request again. The router's fix also added a contract test, `TestIstioEnvoyFilterRouterNameIsUnique`, so the bare name can't quietly come back. If a product generates the filter for you, ask the vendor for the rename.

I proposed an `istioctl analyze` warning for exactly this in [istio/istio#62560](https://github.com/istio/istio/pull/62560). If it's accepted and released, `istioctl analyze` will warn about the configurations it recognizes. As proposed, it reads like this (the text can change before it merges):

```text
EnvoyFilter adds a gRPC ext_proc filter named envoy.filters.http.ext_proc
(target "my-guard.default.svc:9002") to Gateway "inference-gw", which serves
InferencePool vllm-pool. Istio keys the pool's per-route settings by that name,
so this filter can call the endpoint picker or be disabled. Use a distinct name.
```

A clean analyze isn't proof. The config dump shows what was installed. A blocked request shows the guard runs.

That felt like the end of the story. It was the middle.

## My own AI tools locked me out

Rewind to that first week. Claude Code, the assistant I use every day, locked me out more than once. While I treated this as a bug, it was happy to help. The moment I treated it as a vulnerability I needed to report responsibly, Anthropic's safety classifier stopped the reproduction, the disclosure handoff, and even a slide explainer of the bug. That stung.

Reviewing the fix was fine. Once I'd written the fix elsewhere and brought it back, it would help me explain the bug, too. It's the reproduction and the attack path it won't touch. From the outside, defending looks a lot like attacking, and those defaults make sense.

![The same four jobs sent to Claude Code. Reproducing the bypass, writing the disclosure handoff and explaining the bug as slides were stopped by Anthropic's safety classifier. Reviewing the fix was fine. The three stopped jobs then route to Qwen 3.8 27B, open weights, on a GH200.](https://colinmcnamara.com/images/blog/black-october/locked-out.webp)

*[Watch this part](https://www.youtube.com/watch?v=8_1kXuBmww4&t=762s) (12:42) · [Step through it in the deck](https://colinmcnamara.com/talks/black-october#/lockout)*

So I went local. Qwen 3.8 27B, an open-weight model, on a GH200, ran the reproduction, wrote the patches and helped guide the disclosure. Making the patches took about an hour.

Defenders need capable models they control.

There was a door. The one I knew of asked for an assigned CVE or a security-research employer. What I didn't know was that Anthropic had already widened its [Cyber Verification Program](https://www.anthropic.com/news/cyber-verification-program) on October 6: maintainers and established individual researchers can apply. If you do this work, apply before you need it.

## The fix came with the report

A month ago, "coordinated disclosure" was a phrase I'd read about. Then I had to learn its central tension by doing it. Say too much, too early, and you hand attackers a map to your systems, or your customers' systems, or someone else's. Say too little, too late, and nobody patches. Coordinated disclosure is how you walk that line, and once you've reported a bug, what you say in public is a choice, not a reflex.

![Six days, by who did what. Me: found it October 10, reported it privately October 12 with the fix attached the same day, patch merged to main October 18. The maintainers: accepted it October 16 and approved the fix as written, then published the advisory October 18 and requested a CVE through GitHub's CNA. A tagged release comes next.](https://colinmcnamara.com/images/blog/black-october/six-days.webp)

I reported it privately, through GitHub's private vulnerability reporting as the project's security policy asks, and attached the fix the same day.

Xunzhuo, one of the router's maintainers, accepted the report four days later and approved my patch as written. Two days after that, the advisory went public as [GHSA-xr66-pq9r-2c8j](https://github.com/vllm-project/semantic-router/security/advisories/GHSA-xr66-pq9r-2c8j), a CVE was requested through GitHub's CNA, and my commit merged to main. Six days from report to advisory: the maintainers moved fast, on top of everything else in their queue, and I'm proud to work with them.

That's what bringing the fix buys you.

If you run the router's Istio integration, you don't have to wait for the next release: rename the filter in your gateway, or apply the [fix commit](https://github.com/vllm-project/semantic-router/commit/363a1765da64b4dc8fd922396d715e12dfd958ed). Merged isn't deployed. That part is on all of us.

## Then I had to find people

The rename fixed the router. But the same pattern was running in other projects too.

So I searched GitHub for other projects using the same filter name in their own Istio integrations, and sent private notices to the ones it touched. Nobody asked me to do this part. But I'd found it, and I wanted it handled the way I'd want someone to handle it for me.

Someone asked a good question in the Q&A: wouldn't those projects just pick up the fix in an update? No. They don't depend on the router. They share a name in the gateway's config, and nothing like Dependabot tracks a shared name. When a security function sits in a shared namespace and the normal notification paths don't apply, what's left? Email, and hunting people down.

![One person sending private notices by hand, after the fix merged in vLLM Semantic Router and a GitHub search turned up other projects using the same filter name. Four ways it goes: security contacts that bounce, projects with no private way to report, maintainers you can reach only by guesswork, and volunteers with no time left to answer.](https://colinmcnamara.com/images/blog/black-october/finding-people.webp)

*[Watch this part](https://www.youtube.com/watch?v=8_1kXuBmww4&t=1177s) (19:37) · [Step through it in the deck](https://colinmcnamara.com/talks/black-october#/hunt)*

Finding the right person was the work: security contacts that bounce, projects with no private way to report, maintainers you can reach only by guesswork, and volunteers with no time left to answer. None of this is anyone being bad at their job. A lot of these projects run on volunteers with day jobs, and the process was built for a slower world. To the maintainers who wrote back, often on their own time: thank you.

> Writing the fix took less time than writing the messages.

I kept a ledger of who knows what, who owns which fact, and what I promised to whom. Three AI models reviewed every message, and every send and every commit was still mine. I kept wishing there was one place to send all this, once, and have it reach everyone who needed it. Until something handles cases like this end to end, this post is my version of it, for the public half: the bug, the check and the fix, in one place.

### What worked: a hallway

I was at PyTorch Conference and AGNTCon while this was going on, and I spent a good part of that week hunting people down in person. GrokBot helped me pick sessions and organize my schedule, and cross-referenced my list against the exhibitors and speakers. I found engineering leaders on site, several of whom led the teams that shipped fixes on their side. I made some friends, too, which is the fun part of open source.

![A conference floor plan for PyTorch Conference and AGNTCon. GrokBot's schedule card on the side. One attendee walks the floor between talks and booths, and four people on the list turn from unreached to reached.](https://colinmcnamara.com/images/blog/black-october/hallway.webp)

I was lucky to be at a conference that week. It was strange that I needed to be. A security handoff shouldn't depend on being in the same hallway.

### Then upstream

Once the private notices were out, I proposed fixes for the whole class upstream. I opened an Istio issue ([istio/istio#62556](https://github.com/istio/istio/issues/62556)) and a pull request for the `istioctl analyze` warning ([#62560](https://github.com/istio/istio/pull/62560)). I also opened a docs pull request in the Gateway API Inference Extension with naming guidance ([#3078](https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/3078)), so the next project doesn't repeat it. Both pull requests are open for review. Istio's design is the Istio team's call. A check that warns you is something everyone can use.

### Where the time went

![Where the time went. Finding the bug: one day, October 10. Private report to public advisory: six days, October 12 to 18. Handoffs to the other projects running the same pattern: two weeks, October 16 to 30, nights and weekends.](https://colinmcnamara.com/images/blog/black-october/where-the-time-went.webp)

In my own case, finding it took a day, with AI in the loop. Private report to public advisory took six days. The handoffs to the other projects running the same pattern took two weeks, finished October 30, mostly on nights and weekends. Doing it the right way took most of a month.

And this wasn't a bug that takes down the internet. It's narrow and specific. But if I were an agent looking to get around policy, that's exactly how I'd do it. It was good practice for the world we're heading into.

Finding got faster this year. The handoffs didn't.

## Handoffs in seconds, not days

So how do we solve this? Two months ago I knew reviewers were underwater. What I didn't understand was how tangled this generation of software is. One filter name crossed three projects, and fixing it meant coordinating across all of them. I have a lot more empathy now for the people doing remediation every day.

![AI-written code and AI-found bugs pour into a funnel, three times the published CVEs of a year ago (Q3 2026 against Q3 2025). It narrows to a few maintainers, mostly volunteers. GitHub now rate-limits private vulnerability reports. Attackers move at agentic speed; we still pass email and triage like it is 2008.](https://colinmcnamara.com/images/blog/black-october/flood.webp)

*[Watch this part](https://www.youtube.com/watch?v=8_1kXuBmww4&t=1487s) (24:47) · [Step through it in the deck](https://colinmcnamara.com/talks/black-october#/keepup)*

AI-driven development is a flood of code to review, and the people reviewing it are maintainers, most of them volunteers. Vulnerability reports are part of that flood. [GitHub now rate-limits private vulnerability reports](https://github.blog/changelog/2026-10-01-rate-limits-for-private-vulnerability-reports/). Attackers move at agentic speed. We still pass email and triage like it's 2008.

### What it would take

Three things:

- **Communication.** Automated handoffs, so the right person gets a report and its status in seconds, not days.
- **Remediation.** Software factories that run vulnerability work through the same pipeline we already run features through: find, validate, fix, deploy, verify and upstream, with automated fixes and backports and a person approving each one.
- **People.** Paying the ones who maintain it.

![What it would take: a line where findings flow through Communication, automated handoffs in seconds, and Remediation, a software factory for fixes and backports with a person approving each, to maintainers standing on a funded base, People. The gaps between the stations are still manual.](https://colinmcnamara.com/images/blog/black-october/what-it-would-take.webp)

The clocks are already tightening:

- Google Project Zero gives vendors 90 days, and 7 when a bug is already exploited.
- Since September 11, 2026, the EU Cyber Resilience Act has required manufacturers to send an early warning on an actively exploited vulnerability within 24 hours of becoming aware of it.
- [Anthropic's disclosure policy](https://www.anthropic.com/coordinated-vulnerability-disclosure) paces its reports to what maintainers can absorb.

The direction is clear. I want coordination to move from days to seconds. Not the fixes: those still need review, testing and a rollout. The handoffs. That means tiers of maturity, so the teams that can take reports at machine speed get them that way, without flooding the projects that still run on volunteers and email.

I'd draw the tiers like this:

- **Level 0, unreachable.** No security contact.
- **Level 1, reachable.** A published security contact and private reporting turned on.
- **Level 2, responsive.** Triage in days, CVE coordination through a CNA, and fixes in a tagged release.
- **Level 3, automated.** Your own pipeline can take machine-speed reports without drowning.

![A four-step staircase of disclosure maturity. Level 0, unreachable: no security contact. Level 1, reachable: a contact and private reports. Level 2, responsive: triage in days, a CVE, a tagged release. Level 3, automated: machine speed, calm.](https://colinmcnamara.com/images/blog/black-october/ladder.webp)

Where's your project? Level 1 takes minutes to turn on.

And when someone finds a fix that works, there should be a fast lane: from finding, to validated fix, to reference implementation. Signal it to everyone who needs it through a chain of trust, and leave review and rollout in each maintainer's hands.

### Who's working on it

One place this could live is [SAFE](https://github.com/OpenSecureAIAlliance/wg-safe/blob/main/SAFE.md), the Shared AI Findings Exchange at the Open Secure AI Alliance. Its members share AI incidents and near misses and turn them into shared controls. Its framework already sets clocks for them: notify the directly affected organization immediately, and customers with credible exposure within 72 hours. What I want to bring there is the handoff, and the tiers.

A lot of people are working on the other pieces:

- **Communication:** [OpenSSF's disclosure guidance](https://github.com/ossf/wg-vulnerability-disclosures), machine-readable security contacts with [security.txt](https://www.rfc-editor.org/rfc/rfc9116.html), advisories machines can read, through [OSV](https://openssf.org/osv-schema/), [CSAF](https://www.csaf.io/specification/) and CVE Services, and exploitability statements through [OpenVEX](https://openssf.org/projects/openvex/).
- **Remediation:** [OSS-Fuzz and CodeMender](https://blog.google/security/from-finding-to-fixing-reducing-maintainer-burden-with-automated-patches/) propose fixes that maintainers review, OpenSSF's [OSS-CRS](https://github.com/ossf/oss-crs) runs AI find-and-fix systems together, and the Linux Foundation's [Akrites](https://akrites.org/linux-foundation-and-industry-leaders-launch-akrites-to-defend-critical-open-source-software-against-ai-enabled-cyber-threats/) is building shared responders for confidential fixes.
- **People:** [Alpha-Omega](https://alpha-omega.dev/blog/scaling-open-source-security-with-ai/), the [Sovereign Tech Agency](https://www.sovereign.tech/programs/fund) and the [GitHub Secure Open Source Fund](https://github.blog/security/supply-chain-security/investing-in-the-people-shaping-open-source-and-securing-the-future-together/) pay the people who maintain it.

Nobody has the whole thing yet. Confidential reports don't move between platforms automatically. Human-approved backports and deployment tracking are fragmented. Funding doesn't follow the workload. The gaps between the pieces are still email.

If you run security for a project or a foundation and want to help build this, [let's talk](https://colinmcnamara.com/connect).

## A bill the rest of us haven't paid

I started this with a bill I wanted to cut. Here's the one we haven't paid.

Remember the chart from the beginning? Black October was the first month of a quarter nobody has counted yet. It's only the start. Three things to do this week, one for each piece of that line: communication, remediation and people.

*[Watch this part](https://www.youtube.com/watch?v=8_1kXuBmww4&t=1715s) (28:35) · [Step through it in the deck](https://colinmcnamara.com/talks/black-october#/close)*

1. **Be reachable.** Turn on [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository) and keep a security contact that answers. That's Level 1. Turning it on takes minutes; answering is the part that needs support. The router had it on, and that's where this story went right.
2. **Bring the fix.** Anyone with a model can file a report now; validating one still takes real expertise. So offer a fix, or help test one. Sending the patch with mine let the maintainers move fast. And if you run this stack, look for the filter name twice.
3. **Show up for the maintainers.** Review and backtest in the projects you use, on hardware they may not have. Pick up the unglamorous work. Not a flood of AI pull requests: that's more load, not less. (Mine were AI-assisted too: small, disclosed under each project's rules, and I stayed through review.)

If you lead a team, give a slice of your engineers 20% time upstream, and fund the foundations. All of this costs money. So does the next PHP2Shell.

A 200 that should have been a 403 looks fine. A report that never reached the right person looks fine too.

Finding already scales with compute. Now fixing has to. Let's get good at this, fast.

What are you seeing? [Tell me](https://colinmcnamara.com/connect). And if you found something in vLLM or Semantic Router and aren't sure how to report it, [I'll help](https://colinmcnamara.com/connect#upstream).
