Posts

Essays and analysis about MinIO, S3-compatible storage, and the SILO community fork.

Essays and analysis about MinIO, S3-compatible object storage, and the SILO community fork.

MinIO Fork, Promise Kept

Two months ago in “MinIO is Dead, Long Live MinIO,” I promised I’d keep the MinIO fork patched. The recurring objection on HN is fair: can one person actually maintain something like this? The real answer isn’t clicking fork. It’s what happens when CVEs start landing.

Between April 15 and 17, pgsty/minio shipped RELEASE.2026-04-17, closing four CVEs and a handful of related vulnerabilities disclosed in the same window.

gh-release.webp

The scope I committed to originally was narrow: no new features, keep the supply chain running, handle reproducible bugs and security issues as they come in. This release is what it looks like when that promise gets tested.


What happened upstream

In December 2025, MinIO moved the open-source repo to maintenance mode. The README said security fixes would be “evaluated case by case.” In February 2026, the repository was archived and the landing page became “this repository is no longer maintained.”

The SECURITY.md in that same archived repo still says: “we will always provide security updates for the latest release.”

Over the past month, four high-severity and two medium-severity vulnerabilities have been disclosed against the final open-source release.

policy.webp

It’s been 184 days since the last upstream release. Vulnerabilities get disclosed; fixes ship only in the commercial build. The guidance for OSS users is a single line: upgrade to AIStor.

AIStor starts around $100k/year for 400 TiB — roughly S3 pricing, for software you install and operate yourself.

It’s a clean arrangement: archive the repo so there’s no obligation to patch, keep publishing CVE advisories for visibility, and route everyone who reads them toward the commercial product.

Someone still has to patch the old one.


What this release fixes

Full write-ups, CVSS arithmetic, and PoCs are in the release notes. The short version:

  • CVE-2026-33322 (OIDC JWT algorithm confusion, CVSS 9.8): under certain IdP configurations, an attacker who knows the OIDC ClientSecret can mint a token claiming any identity — including consoleAdmin — and MinIO will accept it. Vulnerable window: November 2022 through March 2026. About three and a half years.
  • CVE-2026-33419 (LDAP STS enumeration and brute-force): the login endpoint leaks which usernames are real, and there’s no rate limiting on the subsequent password guessing. End of the chain is an STS credential.
  • CVE-2026-34204 (replication-header metadata injection): a regular PUT or COPY with certain X-Minio-Replication-* headers can write an object into a permanently unreadable state. The data is still on disk; you just can’t read it back out.
  • CVE-2026-39414 (S3 Select memory exhaustion): one request, one OOM.
  • GHSA-hv4r-mvr4-25vw / GHSA-9c4q-hq6p-c237: two signature-verification bypasses on the unsigned-trailer path. Anonymous or forged-signature requests can successfully write objects on certain routes.

Plus the usual dependency cleanup from go-jose, go.opentelemetry.io, and the Go 1.26.2 upgrade itself — about twenty security items in total counting transitive dependencies.

issue.webp


How it got fixed

I said in the earlier post that I’d rely on AI coding agents, and that’s how this round went. My role was closer to “review and decide” than “write code.”

Per-issue flow, roughly:

  1. Codex drafts first. Given the CVE description and relevant code paths, it produces an initial patch.
  2. Claude Code reviews adversarially. Picks holes from the attacker’s side.
  3. Back to Codex. If it agrees with Claude Code’s critique, it reworks. If not, it has to write out why. No silent overrides.
  4. Another round of review by Claude Code, with both sides’ reasoning on the table. Iterate until they converge.
  5. Tests. Codex proposes cases, Claude Code adds more, Codex runs them, Claude Code reviews the results.
  6. I decide. Read the diff, run the tests, merge or send it back with comments.

I didn’t write any of the code in this round. My job was to define the problem, set constraints, pick between approaches, read diffs, run tests, and merge. The GitHub log shows Vonng, Codex, and Claude Code as co-authors — that’s just who did the work.

fix.webp

A few things I noticed about how this runs in practice.

Two heterogeneous agents in opposition catch more than one agent alone. A single agent patching a security bug tends toward confident-sounding fixes that quietly miss a boundary condition. Having a second agent argue against the first filters out most of those.

It forces the tradeoffs into writing. When two implementations diverge, someone has to say why A over B. That exchange is the thing I can actually act on as the person deciding what to merge.

Real maintenance is patch-on-patch, not one-shot. The LDAP STS fix is a good example. The first version landed, and then we realized: successful requests shouldn’t count against the rate limit; X-Forwarded-For shouldn’t be trusted by default; the limiter should key on source IP plus normalized username, not just one. Three follow-up commits before it settled. Iterating through that by hand would have cost a lot more time.


Why this fork exists

Because I use MinIO myself.

MinIO is a production dependency for Pigsty. I need working binaries, a complete console, packages that keep shipping, and someone actually handling CVEs. That keeps the scope narrow. No new features, no turning the repo into a playground. Compatibility, supply chain, fixes when they’re needed.

— Chainguard also ships MinIO container images that track upstream’s post-archive commits, a useful option if you use their images. This fork is a different shape: source tree, RPM/DEB packages, restored console, and doesn’t depend on upstream continuing to push patches somewhere.

The fork is at about 1,300 stars on GitHub and 50,000+ pulls on Docker Hub now. Not remarkable numbers, but enough to tell me I’m not the only one who needed this fork to keep shipping.

credit.webp

If you’re already running OSS MinIO, migration is cheap:

You don’t need to replace anything around it or relearn the API. In most cases, you’re just pointing a compatible binary at the same deployment. If you want a full HA production setup, Pigsty ships one for free.


Something I use broke; I’m fixing it.

What’s different in 2026 is the cost of “I’m fixing it.” With two coding agents and someone to referee between them, the maintenance load of a mid-sized Go codebase is tractable for one person in a way it wasn’t a year or two ago. That’s about it — not a grand theory about open-source resilience, just the current operating point.

If you’re running OSS MinIO, the migration is cheap and the patches are current. If another CVE drops, I’ll still be here.

release.webp

MinIO Is Dead, Long Live MinIO

MinIO’s open-source repo has been officially archived. No more maintenance. End of an era — but open source doesn’t die that easily.

I created a MinIO fork, restored the admin console, rebuilt the binary distribution pipeline, and brought it back to life.

If you’re running MinIO, swap minio/minio for pgsty/minio. Everything else stays the same. (CVE fixed, and the console GUI is back)


The Death Certificate

On December 3, 2025, MinIO announced “maintenance mode” on GitHub. I wrote about it in MinIO Is Dead.

On February 12, 2026, MinIO updated the repo status from “maintenance mode” to “no longer maintained”, then officially archived the repository. Read-only. No PRs, no issues, no contributions accepted. A project with 60k stars and over a billion Docker pulls became a digital tombstone.

archived.webp

If December was the clinical death, this February commit was the death certificate.

Today (Feb 14), a widely circulated article titled How MinIO went from open source darling to cautionary tale laid out the full timeline.

mermaid-timeline.webp

Percona founder Peter Zaitsev also raised concerns about open-source infrastructure sustainability on LinkedIn. The consensus in the international community is clear:

MinIO is done.

Looking back at the timeline over the past years, this wasn’t a sudden death. It was a slow, deliberate wind-down:

DateEventNature
2021-05Apache 2.0 → AGPL v3License change
2022-07Legal action against NutanixLicense enforcement
2023-03Legal action against WekaLicense enforcement
2025-05Admin console removed from CEFeature restriction
2025-10Binary/Docker distribution stoppedSupply chain cut
2025-12Maintenance mode announcedEnd-of-life signal
2026-02Repo archived, no longer maintainedEnd of project

A company that raised $126M at a billion-dollar valuation spent five years methodically dismantling the open-source ecosystem it built.


But Open Source Endures

Normally this is where the story ends — a collective sigh, and everyone moves on.

But I want to tell a different story. Not an obituary — a resurrection.

MinIO Inc. can archive a repo, but they can’t archive the rights that the AGPL grants to the community.

Ironically, AGPL was MinIO’s own choice. They switched from Apache 2.0 to AGPL to use it as leverage in their disputes with Nutanix and Weka — keeping the “open source” label while adding enforcement teeth. But open-source licenses cut both ways — the same license now guarantees the community’s right to fork.

Once code is released under AGPL, the license is irrevocable. You can set a repo to read-only, but you can’t claw back a granted license. That’s the beauty of open-source licensing by design: a company can abandon a project, but it can’t take the code with it.

So — MinIO is dead, but MinIO can live again.

That said, forking is the easy part. Anyone can click the Fork button. The real question isn’t “can we fork it” but “can someone actually maintain it as a production component?”


Why would I do that?

I didn’t set out to take this on. But after MinIO entered maintenance mode, I waited a couple of weeks for someone in the community to step up.

But I didn’t find one. So I did it myself.

Some background: I maintain Pigsty — a batteries-included PostgreSQL distribution with 460+ extensions, cross-built for 14 Linux distros. I also maintain build pipelines for 290 PG extensions, several PG forks, and dozens of Go Projects (Victoria, Prometheus, etc.) packaging across all major platforms. Adding one more to the pipeline was a piece of cake.

I’m not new to MinIO either. Back in 2018, we ran an internal MinIO fork at TanTan (back when it was still Apache 2.0), managing ~25 PB of data — one of the earliest and largest MinIO deployments in China at the time.

More importantly, MinIO is an optional module in Pigsty. Many users run it as the default backup repository for PostgreSQL in production. We did consider several alternatives, but none were a drop-in replacement for MinIO-based workflows.

minio-docs-en.webp

We use MinIO ourselves, so keeping the supply chain alive was not optional — it had to be done. As early as December 2025, when MinIO announced maintenance mode, I had already built CVE-patched binaries and switched to them.

releases.webp

pgsty/minio RELEASE.2025-12-03T12-00-00Z


What We’ve Done

As of today, three things.

1. Restored the Admin Console

This was the change that frustrated the community the most.

In May 2025, MinIO stripped the full admin console from the community edition, leaving behind a bare-bones object browser. User management, bucket policies, access control, lifecycle management — all gone overnight. Want them back? Pay for the enterprise edition. (~$100,000)

We brought it back.

gui.webp

The ironic part: this didn’t even require reverse engineering. You just revert the minio/console submodule to the previous version. They swapped a dependency version to replace the full console with a stripped-down one. The code was always there.

console.webp

We put it back.

2. Rebuilt Binary Distribution

In October 2025, MinIO stopped distributing pre-built binaries and Docker images, leaving only source code. “Use go install to build it yourself” — that was their answer.

For the vast majority of users, the value of open-source software isn’t just a copy of the source — supply chain stability is what matters. You need a stable artifact you can put in a Dockerfile, an Ansible playbook, or a CI/CD pipeline — not a requirement to install a Go compiler before every deployment.

We rebuilt the distribution:

Docker Images
pgsty/minio is live on Docker Hub. docker pull pgsty/minio and you’re good.
RPM / DEB Packages
Built for major Linux distributions, matching the original package specs.
CI/CD Pipeline
Fully automated build workflows on GitHub, ensuring ongoing supply chain stability.

If you’re using Docker, just swap minio/minio for pgsty/minio.

For native Linux installs, grab RPM/DEB packages from the GitHub Release page. You can also use pig (the PG extension package manager) for easy installation, or configure the pigsty-infra APT/DNF repo to install from it:

curl https://repo.pigsty.io/pig | bash; 
pig repo add infra -u; pig install minio

Just works as usual.

3. Restored Community Edition Docs

MinIO’s official documentation was also at risk — links had started redirecting to their commercial product, AIStor.

We forked minio/docs, fixed broken links, restored removed console documentation, and deployed it here.

The docs use the same CC Attribution 4.0 license as the original, with necessary maintenance.

doc.webp


Commitments

Some things worth stating up front to set expectations.

No New Features — Just Supply Chain Continuity

MinIO as an S3-compatible object store is already feature-complete. It’s a finished software. It doesn’t need more bells and whistles — it needs a stable, reliable, continuously available build. (I already have PostgreSQL for these, so I don’t need something like S3 table or S3 vector. A stable S3 core is all I need)

What we’re doing: making sure you can get a working, complete MinIO binary, with the admin console included and CVE fixed. RPM, DEB, Docker images — built automatically via CI/CD, drop-in compatible with your existing minio. We keep the existing minio naming and behavior where legally and technically feasible.

This Is a Production Build, Not an Archive

We run these builds ourselves and have been dogfooding them in production for three months. If something breaks, we detect it early and patch it quickly.

I build this primarily for Pigsty and our own usage, but I hope it helps others too.

I’m willing to Track CVEs and Fix Bugs

If you run into issues, feel free to report them at pgsty/minio. I’ll do my best to fix these — but please don’t treat this as a commercial SLA.

Given that AI coding tools have made bug fixing dramatically cheaper, and that we’re explicitly not adding any new features, I believe the maintenance workload is manageable. (how often do you see one?)

Trademark Is Tricky, But We’ll Cross That Bridge When We Come to It

Disclaimer

Trademark Notice: MinIO® is a registered trademark of MinIO, Inc. This project (pgsty/minio) is an independently maintained community fork under the AGPL license. It has no affiliation with, endorsement by, or connection to MinIO, Inc. Use of “MinIO” in this post refers solely to the open-source software project itself and implies no commercial association.

AGPLv3 gives us clear rights to fork and distribute, but trademark law is a separate domain. We’ve marked this clearly everywhere as an independent community-maintained build.

If MinIO Inc. raises trademark concerns, we’ll cooperate and rename (probably something like silo or stow). Until then, we think descriptive use of the original name in an AGPL fork is reasonable — and renaming all the minio references doesn’t serve users.

AI Changed the Game

You might ask: can one person really maintain this?

It’s 2026. Things are different now.

AI coding tools are changing the economics of open-source maintenance.

With tools like Claude Code & Codex, the cost of locating and fixing bugs in a complex Go project has dropped by more than an order of magnitude. What used to require a dedicated team to maintain a complex infra project can now be handled by one experienced engineer with an AI copilot.

Maintaining a MinIO build without adding new features is a manageable task. The key requirement is testing and validation. and we already have that scenario, which lets us verify compatibility, reliability, and security in practice.

Consider: Elon cut X/Twitter’s engineering team down to ~30 people and the system still runs. Maintaining a MinIO fork without new features is considerably less daunting


Just Fork It

MinIO Inc. can archive a GitHub repo, but they can’t archive the demand behind 60k stars, or the dependency graph behind a billion Docker pulls. That demand doesn’t disappear — it just finds its way out.

HashiCorp’s Terraform got forked into OpenTofu, and it’s doing fine. MinIO’s situation is actually more favorable — AGPL is more permissive for forks than BSL, with no legal gray area for community forks. A company can abandon a project, but open-source licenses are specifically designed so the code can’t die.

Fork is the most powerful spell in open source. When a company decides to shut the door, the community only needs two words:

Fork it.


Reference

Originally published in Chinese

MinIO Is Dead. Which Next?

MinIO announced maintenance mode two days ago. I ranted in “MinIO Is Dead” and immediately got flooded with “so what now?”

The usual suspects: Ceph, RustFS, SeaweedFS, Garage. I packaged all of them for Linux (RPM/DEB) and ran them through the grinder.

Short version: there’s no perfect substitute. Ceph is powerful but overkill; SeaweedFS rocks tiny files but needs an external metadata DB; Garage is cute but too barebones; RustFS targets the MinIO niche but is still alpha.

Quick scan of the field

MinIO is the open-source S3 clone. If all you need is basic object CRUD, any S3-compatible store works. But parity with MinIO means more than APIs—it’s about reliability, operability, tooling, documentation, SOPs. Replacing it cleanly is hard.

Ignoring commercial clouds, here’s the OSS menu:

  • Ceph – arguably the best choice for enterprises, but brutally complex. Most folks don’t need block + file + object in one, and it requires extras like Podmon. MinIO’s single binary spoiled us.
  • SeaweedFS – optimized for oceans of small files; O(1) disk seeks make it absurdly fast there. But it relies on an external metadata store. If you want a general-purpose object store, that dependency is annoying.
  • Garage – built by Deuxfleurs with NGI funding. Delightfully light (10 MB), great for self-hosters and edge nodes. But S3 compatibility is thin: no versioning, no cross-region replication, no IAM. Enterprises will laugh.
  • RustFS – the only project explicitly chasing “drop-in MinIO,” but it’s still alpha.

RustFS vs. MinIO

RustFS looked the most promising, so I wired it into Pigsty as a MinIO replacement. Most logic carried over, but a few differences popped up:

  • Certificates must follow specific naming rules.
  • Health checks differ from MinIO’s endpoints.
  • mc admin doesn’t work; you can’t push fine-grained IAM policies. That’s a deal-breaker for many teams.

It ran, but I’m not shipping alpha software into production, so I shelved the branch. I’ll revisit when RustFS hits GA.

Will RustFS repeat MinIO’s mistakes?

RustFS has potential, but I worry it’ll retrace MinIO’s path. I asked the AI big three (GPT‑5 Pro, Claude 4 Opus, Gemini 3 Pro) to audit the project. Gemini leveled some serious accusations; Claude corroborated.

The red flags match MinIO’s history: Apache 2.0 license + copyright assignment CLA + single commercial gatekeeper. With that risk profile, I’m downgrading RustFS from “optimistic” to “cautious wait-and-see.”


So what now?

Pigsty bundles MinIO as an optional module for PostgreSQL backups or as an on-prem S3 for apps like Supabase. After surveying the alternatives, I’m not eager to swap it out. I might add a pgBackRest-native backup server option, but ripping out MinIO today feels premature.

Best plan: stay on the latest MinIO release, lock the version, isolate it on the network, and wait a few months. Maybe the community forks it; maybe RustFS matures. Adjust when reality changes.

RustFS still has a golden window to seize MinIO’s niche with a safer, community-friendly fork. That window is measured in months, not years.


If you stick with MinIO

Use the latest build, not the April 22, 2025 edition with the GUI. There’s a serious CVE in the interim:

  • CVE-2025-62506 – privilege escalation via session-policy bypass (HIGH). Low-privilege users can mint new accounts and escalate.

In a locked-down intranet the risk is manageable, but you still want the fix, which landed in the 2025‑10‑15 release. MinIO pulled the prebuilt binaries starting with that version, offering source only. Annoying, but it’s Go—go build and you’re done. I forked MinIO, ran their packager, and produced RPM/DEBs for 2025‑12‑03 so I’m not deploying vulnerable bits: https://github.com/pgsty/minio

minio.png

Security patches still need humans. MinIO claims they’ll fix critical issues, but if the community wants a maintained fork, now’s the moment. Start from 2025‑04‑22, cherry-pick critical bug/security fixes, and keep a community LTS alive.

MinIO is “done” software. It doesn’t need the latest S3 gimmick (Vector/Table); it needs steady bugfixes. That’s perfect for a community branch. Plenty of storage vendors rely on MinIO; maintaining a fork beats writing a new object store from scratch.

2026-02-14 Update: MinIO’s official repo has been fully archived and is no longer maintained. Besides, I’ve personally maintained an oss fork of minio: pgsty/minio / Docs: https://silo.pigsty.io. Which based on the last upstream version 2025-12-03 with restored console capabilities.

MinIO is Dead

December 3, 2025 was a day to mark in open-source software history. MinIO’s team updated the project status on GitHub, announcing the MinIO open-source project was entering “maintenance mode.” This basically declared the death of MinIO as an open-source project.

MinIO the company has finally completed its transformation from a dragon-slaying hero into the very dragon it once sought to slay.

maintenance-mode.png


From Dragon-Slayer to Dragon

Democratization Era (2014–2019): The Apache of Object Storage

MinIO was founded in 2014 with a highly idealistic vision – to be “the Apache of object storage.” In an era dominated by AWS S3, MinIO’s ultra-lightweight design (a single static binary) and 100% S3 API compatibility quickly won developers’ hearts.

During this phase, MinIO was licensed under the liberal Apache 2.0 license, encouraging developers to integrate it into all kinds of applications. Its core pitch: “turn any hardware into AWS S3.” This open strategy was wildly successful. MinIO claimed its Docker image had been pulled over 1 billion times, making it the world’s most widely deployed object storage service. At this point, MinIO was a darling of the cloud-native stack – the default storage backend in many Kubernetes setups.

License Weaponization (2019–2025): The AGPL War

The first major crack in community relations appeared around 2019–2021. MinIO announced it was changing its core license from Apache 2.0 to GNU AGPLv3.

The official explanation was that this move aimed to prevent cloud providers (like AWS, Azure) from “freeloading” the code and repackaging it as proprietary services — a common defensive tactic in open source. During this period, MinIO shifted from being a community guardian to an aggressive defender of its IP. In 2022, MinIO publicly accused Nutanix Objects of violating its license and revoked Nutanix’s right to use MinIO; in 2023, MinIO sued high-performance filesystem vendor Weka on similar grounds. These legal actions, though legally contentious, sent a clear signal: MinIO no longer welcomed commercial use without paying up. This set the legal and psychological stage for the full lockdown that would come in 2025.

Control Plane Neutered (May 2025)

In May 2025, MinIO decided to strip the MinIO Console out of the community edition. The console was a critical GUI for bucket management, IAM, monitoring, and audit logging. After this removal, the open-source MinIO was left with only a basic “object browser” GUI – essentially just a file viewer/downloader.

Meanwhile, key admin features like policy management, site replication configuration, and lifecycle management were moved entirely into the commercial enterprise edition. This change downgraded the open-source MinIO from a full-featured storage management system into a mere data-plane component, robbing it of the control-plane capabilities needed to run as a standalone product in production.

Cutting Off Binary Distribution (Oct 2025)

On October 15, 2025 – right as a critical security vulnerability (CVE-2025-10-15T17-29-55Z / GHSA-jjjj-jwhf-8rgr) was disclosed – MinIO stopped publishing updated Docker images to Docker Hub and Quay.io. The timing of this move was highly strategic. By cutting off binaries during a major security incident, MinIO effectively used security as a bargaining chip.

This decision directly broke the automated deployment pipelines for countless users. Helm charts, Ansible playbooks, and Terraform scripts expecting minio/minio (or Bitnami’s minio) image suddenly failed to find updates. Auto-scaling groups trying to pull new nodes hung due to missing images. For teams without a Go build environment or an internal container registry, MinIO instantly became unusable.

Maintenance Mode (Dec 2025)

On December 3, 2025, MinIO, Inc. officially updated its channels and GitHub repo to announce that the open-source project is now in “maintenance mode.” The README stated that there will be no further feature additions or improvements, issues and PRs will no longer be reviewed, and even critical security fixes would be provided “as appropriate.” No more RPM/DEB packages or Docker images will be released. Essentially, anyone needing updates or support is advised to switch to the commercial AIStor product.

aistor.png


Technical Impact: Damage to the Open-Source Ecosystem

MinIO’s move to maintenance mode dealt an immediate and far-reaching blow to many tech stacks.

Broken CI/CD Pipelines and an Automation Crisis

Thousands of Helm charts, Ansible playbooks, and Terraform scripts depend on the minio/minio (or Bitnami’s minio) container image. With official images no longer published, third-party packagers like Bitnami — who can’t get a stable upstream release — also had to stop updates.

  • Cascade effect: Deployments in fresh environments started failing outright. Auto-scaling groups, upon launching new instances, would hang or error out when the MinIO image couldn’t be pulled.
  • Cost of fixes: Companies now have to rewrite their deployment scripts to point to a self-hosted image, and set up internal build pipelines to compile and package MinIO from source.

Security Vacuum: CVE Patches Go Private

The most lethal consequence of halting binary distribution is delayed security patches. In the October 2025 incident, for example, MinIO effectively withheld the patched binaries for the vulnerability.

  • Risk exposure: Companies without dedicated security teams are forced to keep running older, vulnerable versions with known critical flaws.
  • Compliance nightmare: For organizations under PCI-DSS, HIPAA, SOC2, etc., not being able to obtain vendor-signed security updates is a compliance disaster. Lacking official patches, they technically fall out of compliance.

Exponentially Higher Ops Complexity

Removing the UI wasn’t just a hit to user experience – it increased operational burden. Tasks that used to be a few clicks in the Console (configuring bucket policies, setting user permissions) now require ops engineers to master the mc CLI or hand-craft complex JSON policy docs. This raises the skill floor and makes MinIO far less friendly as a lightweight internal tool.


Underlying Reasons: Pressure from Capital and Commercialization

The driving force behind MinIO’s decisions is the logic of venture capital. By 2025, MinIO had raised a total of $126 million in funding. The most significant was a $103 million Series B in January 2022 led by Intel Capital, SoftBank Vision Fund II, and General Catalyst, which crowned MinIO a unicorn (valued over $1 billion).

In VC terms, a $1B valuation means the company must show a clear path to IPO — typically demanding $100M+ in Annual Recurring Revenue (ARR) and rapid growth. In Feb 2025, MinIO announced its ARR had grown 149% over the past two years businesswire.com. Impressive growth, but to live up to a sky-high valuation, organic conversion alone wasn’t enough.

Cutting off the free open-source offering is the most direct way to force a huge user base into paid customers.

In 2025, MinIO underwent a full rebrand and launched “MinIO AIStor,” styling itself as “the data backbone for enterprise AI.” Management recognized that general-purpose object storage (for backups, file servers, etc.) was a red-ocean market with thin margins, whereas generative AI’s appetite for high-throughput data (the exascale AI era) promised the next big surge. By tuning its product for AI workloads and focusing on Fortune 500 enterprises linkedin.com, MinIO essentially decided to cut loose its low-value open-source user base. The move to maintenance mode signaled MinIO’s official pivot from a broad open-source project into a vertical, high-end AI software vendor.

MinIO isn’t a garage hobby project by a few geeks anymore; it’s a company that took $126M in VC and is valued at over $1B. Backed by Intel Capital and SoftBank, once you take that money, your boss is no longer the users — it’s the investors. And what do investors want? ARR, growth, IPO. You tell them, “We have a billion Docker pulls!” and they’ll ask, “How many dimes did those pulls pay us?”

The reality is brutal. To the VCs, those small businesses and individual devs using free MinIO are low-value assets. They open issues and ask for support — consuming expensive engineer time, bandwidth, and servers — yet will never convert to paying customers. MinIO’s leadership knows their real cash cows are the Fortune 500 firms doing generative AI. The ones training GPT models or running self-driving pipelines need AIStor, ultra-high performance, and 24/7 enterprise SLAs.

So flipping the project into “maintenance mode” is essentially an asset carve-out. MinIO is cutting away the “dead weight” (free users) and concentrating on the milkable “cash cows” (enterprise AI clients). In business strategy this is called focus. To the investors, it’s being responsible. But from the perspective of open source, it’s simply betrayal.


Personal Reflections

I started using MinIO around 2018 (back when it was Apache-licensed). We built a few multi-petabyte object storage clusters for videos, images, backups — probably one of the largest MinIO deployments in China at the time. I wrote deployment/monitoring playbooks for MinIO (still open-sourced in Pigsty).

As an open-source startup founder, I can understand the motivation behind these moves. But as an open-source contributor and user — I also know many folks right now have one phrase in their minds: “I have never seen such shamelessness.”

An open-source license isn’t a shackle, but it is a social contract. Developers contribute code, users contribute testing, feedback, and reputation; together, they make a project successful. MinIO enjoyed a decade of community goodwill and parlayed the bragging rights of “#1 in global downloads” into venture funding. Then it turned around and told the very users who propped it up: “You free-riders, get lost.” This kind of move breaks the fundamental trust that open source is built on.

This “bait and switch” tactic is even more nauseating than a crypto rug pull. A rug pull only takes your money — MinIO is pulling the rug out from under the tech stacks of thousands of companies. Adopting a technology isn’t just picking up a binary; it’s buying into an ecosystem and a design philosophy. They got everyone onboard, let the switching costs pile up sky-high, and then suddenly kicked away the ladder. In fact, as open-source expert Tison thoroughly discussed in his article The Bait-and-Switch Open-Source Strategy, the core issue with this model is deception.

MinIO betrayed the community, so the community may abandon it as well. Alternatives like Garage, SeaweedFS, or the new RustFS are ready to step in.

If I have to sum up my feelings, I’d borrow a line from The Hitchhiker’s Guide to the Galaxy:

—— “So long, and thanks for all the fish.”

2026-02-14 Update: MinIO’s official repo has been fully archived and is no longer maintained. Besides, I’ve personally maintained an oss fork of minio: pgsty/minio / Docs: https://silo.pigsty.io. Which based on the last upstream version 2025-12-03 with restored console capabilities.

Release Notes

Detailed notes for every published SILO release, ordered from newest to oldest.

Each published SILO version has its own page with the release date, major changes, security fixes, dependency updates, and related commits.

SILO 20260618 Released

LDAP STS hardening, complete S3 Select record limits, ReadMultiple removal, Go 1.26.4, and security dependency updates.

Published: 2026-06-18 · Version: RELEASE.2026-06-18T00-00-00Z

This release is a security and dependency-maintenance update for the pgsty/minio fork. It hardens LDAP STS throttling, completes S3 Select oversized-record enforcement, removes the obsolete ReadMultiple internode storage-REST API, upgrades the Go build baseline to 1.26.4, and refreshes Go module dependencies to pick up additional third-party security fixes.

Major Changes

  • Remove the obsolete ReadMultiple storage-REST API: the legacy /rmpl internode endpoint is removed rather than patched in place, including its route, handler, client wrapper, storage interfaces, xlStorage methods, generated datatypes, and related metric. No production caller is expected after upstream multipart handling moved to ReadParts, but clusters should still run a consistent release during rolling upgrades.
  • Complete S3 Select oversized-record enforcement: JSON Lines input now uses the bounded reader path, so oversized records are rejected consistently instead of bypassing limits on SIMD-capable CPUs. S3 Select stream errors now preserve the intended error code and wrap JSON parser failures as JSONParsingError.
  • Harden LDAP STS rate-limit source bucketing: throttling is now keyed only by source IP, avoiding username-shared buckets that could be drained by one client to lock out a legitimate user. Trusted-proxy handling now resolves X-Forwarded-For from right to left, rejects catch-all trusted-proxy CIDRs, ignores RFC 7239 Forwarded, and documents the X-Real-IP deployment contract.
  • Refresh the Go runtime and module baseline: release, hotfix, goreleaser, and old-CPU Docker builds now use golang:1.26.4-alpine; go.mod is updated to Go 1.26.4; and dependencies are refreshed across NATS, Prometheus, Azure SDK, Apache Thrift, gRPC, OpenTelemetry, Google API/auth, Go x/*, and related transitive libraries.

Direct Security Fixes

  • CVE-2026-42600: remove the obsolete ReadMultiple storage-REST API to close the legacy internode file-read path exposed through /rmpl.
  • CVE-2026-39414: complete oversized S3 Select record enforcement for JSON Lines inputs and preserve correct S3 Select error semantics.
  • CVE-2026-33419: further harden LDAP STS rate-limit accounting and trusted-proxy source-IP handling.

Dependency Security Updates

  • Update github.com/Azure/go-ntlmssp from v0.1.0 to v0.1.1, fixing CVE-2026-32952, where malformed NTLM challenges could panic a Go process.
  • Update github.com/apache/thrift from v0.22.0 to v0.23.0, fixing CVE-2026-41602 in the Go TFramedTransport implementation.
  • Update github.com/nats-io/nats-server/v2 from v2.11.1 to v2.11.15, absorbing the NATS 2.11.x security patch line. Notable fixes include pre-auth WebSocket and leafnode denial-of-service issues, MQTT authorization issues, JetStream management API authorization hardening, credential exposure fixes, and request identity-spoofing fixes, including CVE-2026-27889, CVE-2026-29785, CVE-2026-33217, CVE-2026-33218, CVE-2026-33222, and CVE-2026-33247.
  • Update github.com/prometheus/prometheus from v0.310.0 to v0.311.3, absorbing Prometheus security fixes for remote-read denial of service, stored XSS in UI surfaces, and remote-write configuration secret exposure, including CVE-2026-42154, CVE-2026-44903, CVE-2026-42151, and CVE-2026-40179.
  • Upgrade the release build baseline through Go 1.26.4 and refresh supporting Go module families, including golang.org/x/crypto, golang.org/x/net, golang.org/x/sys, golang.org/x/text, google.golang.org/grpc, and OpenTelemetry. These updates keep the fork aligned with patched upstream dependency baselines even where the previously pinned version was already past the specific public advisory range.
  • 5e40665: fix: harden LDAP STS rate-limit source bucketing
  • fd69c89: fix: complete CVE-2026-39414 S3 Select record limit enforcement
  • 73ac524: fix: CVE-2026-42600 remove ReadMultiple storage-REST API
  • df627ff: fix: bump Go toolchain to 1.26.4
  • 3e61b1d: chore: update Go module dependencies

SILO 20260417 Released

Security hardening across OIDC, LDAP STS, S3 Select, replication metadata, unsigned-trailer flows, and the Go toolchain.

Published: 2026-04-17 · Version: RELEASE.2026-04-17T00-00-00Z

This release focuses on security hardening and compatibility tightening. It bundles fixes across OIDC, LDAP STS, S3 Select, replication metadata handling, unsigned-trailer flows, the Snowball upload path, and multiple dependency- and Go toolchain-related security issues, while also incorporating the LDAP TLS regression fix and a cleanup of community-fork documentation.

Major Changes

  • Tighten the identity-authentication flow: OIDC / WebIdentity now accepts only asymmetrically signed ID Token values backed by the IdP JWKS; symmetrically signed tokens such as HS256 are no longer accepted. LDAP STS also now hides the distinction between unknown-user and bad-password failures to reduce username-enumeration risk.
  • Update LDAP STS rate limiting: limits now apply to both source IP and normalized username, and successful requests no longer consume quota incorrectly. By default MinIO now uses only the socket peer address as the source and no longer trusts X-Forwarded-For, X-Real-IP, or Forwarded; to rate-limit by real client IP, configure MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES explicitly.
  • Make upload and write paths stricter: presigned query parameters can no longer be combined with unsigned-trailer PUT or multipart uploads. Snowball auto-extract now also performs full signature validation on the unsigned-trailer path and rejects anonymous or forged-signature requests.
  • Prevent replication metadata spoofing: internal X-Minio-Replication-* headers attached to ordinary PUT / COPY requests are now rejected or ignored, and only trusted replication flows may write the related internal metadata.
  • Clarify S3 Select error semantics: oversized CSV and line-delimited JSON records now return OverMaxRecordSize directly instead of the generic InternalError; clients or alerting rules that depend on the old error code should be adjusted.
  • Upgrade the runtime and dependency baseline: fix the regression where ldaps:// did not correctly apply TLS settings, replace minio/pkg/v3 with pgsty/minio-pkg/v3, and pin several critical dependencies that are prone to breaking changes. The release also upgrades go-jose, go.opentelemetry.io, and Go 1.26.2 to unify the build and release baseline.
  • Refresh documentation and security guidance: update SECURITY.md, VULNERABILITY_REPORT.md, docs/sts/ldap.md, and related documents, add a security advisory index, and switch upstream minio/minio references in the security guidance over to pgsty/minio.

Fixed CVEs

  • CVE-2026-34986: upgrade go-jose to v4.1.4 and fix known security issues in the JWT / JOSE dependency chain.
  • CVE-2026-39883: upgrade the go.opentelemetry.io dependency stack to fix the PATH-hijacking risk.
  • CVE-2026-33322: restore the strict JWKS-only OIDC JWT verification path to block keyring injection and algorithm-confusion risk.
  • CVE-2026-33419: systematically harden LDAP STS authentication, rate limiting, source-address identification, and accounting logic across four follow-up fixes.
  • CVE-2026-34204: reject injection of X-Minio-Replication-* metadata by untrusted requests to prevent objects from being written with invalid replication state.
  • CVE-2026-39414: reject oversized S3 Select records early to avoid continued buffering and parsing of abnormal inputs.
  • GHSA-hv4r-mvr4-25vw: close the unsigned-trailer query-auth bypass.
  • GHSA-9c4q-hq6p-c237: harden unsigned-trailer authentication and signature validation in Snowball auto-extract scenarios.
  • CVE-2026-32280, CVE-2026-32281, and CVE-2026-32283: upgrade Go to 1.26.2 and absorb the upstream toolchain and stdlib security fixes.
  • c878ca0: fix: pin deps with breaking changes and fix LDAP TLS regression (#15)
  • e970ec5: fix: upgrade go-jose to v4.1.4 to patch CVE-2026-34986
  • a206510: fix: CVE-2026-39883 upgrade go.opentelemetry.io
  • fd65f11: merge: PR #18 upgrade go-jose to v4.1.4 for CVE-2026-34986
  • bc087e4: merge: PR #19 upgrade go.opentelemetry.io for CVE-2026-39883
  • f1f2239: fix: CVE-2026-33322 restore JWKS-only OIDC JWT verification
  • 6619d0c: fix: CVE-2026-33419 harden LDAP STS auth
  • fcb8f24: fix: CVE-2026-34204 reject untrusted replication metadata
  • c5765dc: fix: CVE-2026-39414 reject oversized S3 Select records
  • fa7c579: fix: GHSA-hv4r-mvr4-25vw block unsigned-trailer query auth bypass
  • b50ab58: fix: GHSA-9c4q-hq6p-c237 harden Snowball unsigned-trailer auth
  • 9a4b3cd: fix: CVE-2026-32280/CVE-2026-32281/CVE-2026-32283 upgrade Go to 1.26.2
  • c55b52c: fix: CVE-2026-33419 preserve LDAP STS rate limits on success
  • 817a457: fix: CVE-2026-33419 harden LDAP STS rate-limit source IP
  • 084a154: fix: CVE-2026-33419 tighten LDAP STS rate-limit accounting
  • 16e34f9: docs: refresh security guidance and fork references

SILO 20260325 Released

Packaging, stability, LDAP TLS, Docker image, and dependency-security maintenance.

Published: 2026-03-25 · Version: RELEASE.2026-03-25T00-00-00Z

This is a maintenance release centered on packaging, stability, and security disclosure. It improves the shipping artifacts, fixes an LDAP TLS regression, and explicitly documents the secure dependency set carried by the release.

Major Changes

  • Bundle mcli/mc into the Docker image and add checksum verification for a better out-of-the-box image experience.
  • Fix the LDAP TLS regression affecting ldaps:// deployments so TLS settings are correctly honored.
  • Remove inherited upstream CI/CD workflows that are no longer used in the community-maintained fork.
  • Pin several critical dependencies to avoid further fallout from upstream breaking changes.

Fixed CVEs

  • CVE-2026-24051: the release notes explicitly call out go.opentelemetry.io/otel/sdk v1.42.0, which avoids the macOS PATH-hijacking arbitrary code execution issue.
  • CVE-2025-10543: the release notes explicitly ship github.com/eclipse/paho.mqtt.golang v1.5.1, fixing incorrect MQTT packet encoding for oversized UTF-8 strings.
  • CVE-2025-58181: the release notes explicitly ship golang.org/x/crypto v0.49.0, fixing unbounded memory consumption in ssh GSSAPI authentication handling.
  • f2f9a40: add mcli/mc from pgsty/mc to Docker image
  • ce1c537: fix: pin deps with breaking changes and fix LDAP TLS regression (#15)
  • ee55e53: remove upstream CI/CD workflows inherited from minio/minio

SILO 20260321 Released

Go 1.26.1, stricter compiler and linter compatibility, and a broad security dependency refresh.

Published: 2026-03-21 · Version: RELEASE.2026-03-21T00-00-00Z

This maintenance release is built around the Go 1.26.1 upgrade and a broad dependency refresh. Beyond stricter compiler and linter compatibility fixes, it also delivers the most substantial security dependency refresh in the current release line.

Major Changes

  • Upgrade the build environment from Go 1.26.0 to Go 1.26.1.
  • Refresh direct and indirect dependencies to converge on the newer toolchain.
  • Fix linter and test issues exposed by the stricter Go 1.26.1 checks.

Fixed CVEs

  • CVE-2026-27137: Go stdlib 1.26.0 -> 1.26.1 fixes incomplete email-constraint enforcement in crypto/x509.
  • CVE-2026-27138: Go stdlib 1.26.0 -> 1.26.1 fixes a crypto/x509 panic triggered by malformed certificates.
  • CVE-2026-25679: Go stdlib 1.26.0 -> 1.26.1 fixes insufficient validation of IPv6 host literals in net/url.
  • CVE-2026-27139: Go stdlib 1.26.0 -> 1.26.1 fixes FileInfo metadata escaping the Root boundary in os.
  • CVE-2026-27142: Go stdlib 1.26.0 -> 1.26.1 fixes missing URL escaping in html/template for meta refresh content.
  • CVE-2026-26958: filippo.io/edwards25519 v1.1.0 -> v1.2.0 fixes incorrect or undefined MultiScalarMult behavior.
  • CVE-2025-10543: github.com/eclipse/paho.mqtt.golang v1.5.0 -> v1.5.1 fixes incorrect MQTT packet encoding for oversized UTF-8 strings.
  • CVE-2026-24051: go.opentelemetry.io/otel/sdk v1.38.0 -> v1.42.0 fixes the macOS PATH-hijacking arbitrary code execution issue.
  • CVE-2026-33186: google.golang.org/grpc v1.77.0 -> v1.79.3 fixes authorization bypass caused by a missing leading slash in the HTTP/2 :path pseudo-header.
  • 5abd9a8: bump golang to 1.26.1 and update deps
  • 377fc61: fix: satisfy stricter Go 1.26.1 linter checks

SILO 20260314 Released

Migration to the community-maintained Console fork and a large compatibility and dependency refresh.

Published: 2026-03-14 · Version: RELEASE.2026-03-14T12-00-00Z

This release switches the project to the community-maintained Console fork and performs a sizeable dependency refresh to establish the base for the later Go 1.26.x maintenance releases.

Major Changes

  • Switch to the community-maintained georgmangold/console v1.9.1 fork in place of the unmaintainable upstream Console dependency.
  • Refresh a large portion of the direct and indirect dependency graph so the new Console and toolchain combination builds cleanly.
  • Fix the go vet format directive issue in grid_test.go and adjust tests for the HTTP behavior changes in Go 1.26.

Fixed CVEs

  • CVE-2025-47913: golang.org/x/crypto v0.37.0 -> v0.46.0 fixes a panic in ssh/agent when handling malformed responses.
  • CVE-2025-58181: golang.org/x/crypto v0.37.0 -> v0.46.0 fixes unbounded memory consumption in ssh GSSAPI authentication parsing.
  • CVE-2025-47914: golang.org/x/crypto v0.37.0 -> v0.46.0 fixes a panic in ssh/agent caused by malformed identity messages.
  • CVE-2025-47911: golang.org/x/net v0.39.0 -> v0.48.0 fixes quadratic parsing complexity in html.Parse for crafted inputs.
  • CVE-2025-58190: golang.org/x/net v0.39.0 -> v0.48.0 fixes an infinite parsing loop in golang.org/x/net/html.
  • 68521b3: add github ci/cd pipeline
  • 00f3cf7: RELEASE.2026-03-14T12-00-00Z with go 1.26.0

SILO 20260214 Released

Embedded Console restoration, GitHub CI/CD, Go 1.26.0, and the first complete community delivery surface.

Published: 2026-02-14 · Version: RELEASE.2026-02-14T12-00-00Z

This early infrastructure-focused community release restores the embedded Console, introduces GitHub CI/CD, and lifts the Go baseline to 1.26.0, which also absorbs a batch of security fixes from the older toolchain generation.

Major Changes

  • Restore the embedded Console and refresh the README to clarify the community fork position.
  • Add GitHub CI/CD workflows as the base for automated builds and multi-platform delivery.
  • Add quick links for docs, Docker, the GitHub repository, and installation through the pig package manager.

Fixed CVEs

These issues were absorbed as part of the Go 1.25.5 -> 1.26.0 upgrade:

  • CVE-2025-68121: crypto/tls could incorrectly accept mutated CA configuration during session resumption.
  • CVE-2025-61730: TLS 1.3 could process handshake messages incorrectly across encryption-level boundaries.
  • CVE-2025-61726: net/url query parsing could be abused for memory exhaustion.
  • CVE-2025-61728: archive/zip could consume excessive CPU while building archive indexes.
  • CVE-2025-68119: cmd/go could trigger unexpected code execution when invoking external VCS tooling.
  • CVE-2025-61731: the #cgo pkg-config: directive could be abused for arbitrary file writes.
  • CVE-2025-61732: cmd/cgo comment parsing discrepancies could enable code smuggling.
  • 8630937: Restore embedded console and update README for community fork
  • 5d57938: add github ci/cd pipeline

SILO 20251203 Released

The first community packaging and distribution baseline, with APK, DEB, and RPM artifacts.

Published: 2025-12-15 · Version: RELEASE.2025-12-03T12-00-00Z

This is the earliest traceable community release. Its purpose is to establish the community packaging and distribution baseline rather than to deliver incremental fixes over an earlier community release.

Major Changes

  • Build the community packaging flow around minio/pkger.
  • Choose a maintenance-mode upstream MinIO baseline as the starting point for the community-maintained fork.
  • Produce the first apk, deb, and rpm artifacts for ongoing community releases.

Fixed CVEs

  • This is the first community release. The GitHub Release does not provide a delta-style security-fix list against an earlier community version, and this page does not attempt to reconstruct the full historical CVE delta against the upstream maintenance baseline.
  • d4cd4b4: RELEASE.2025-12-03T12-00-00Z with go 1.25.5

SILO Security Chronicle

A chronological account of every application-level CVE investigated by the SILO fork, one incident per article.

This is the security chronicle of the SILO community fork. It follows the incidents in the order they were investigated and fixed. Each CVE has its own article: the original threat model, the turns taken during review, the rejected alternatives, the final invariant, the evidence, and the compatibility cost all stay with that incident.

Chronicle

DateCVEIncidentFirst containing release
2026-04-15CVE-2026-32285The jsonparser advisory that required no patchAlready fixed in the dependency graph
2026-04-15CVE-2026-33322OIDC JWT algorithm confusionSILO 2026-04-17
2026-04-15CVE-2026-33419LDAP STS enumeration and throttlingSILO 2026-04-17; completed in 2026-06-18
2026-04-15CVE-2026-34204Replication metadata injectionSILO 2026-04-17
2026-04-15CVE-2026-39414Oversized records in S3 SelectSILO 2026-04-17; completed in 2026-06-18
2026-04-16CVE-2026-40344Snowball auto-extract authentication bypassSILO 2026-04-17
2026-04-16CVE-2026-41145Unsigned-trailer query authentication bypassSILO 2026-04-17
2026-06-12CVE-2026-42600ReadMultiple storage-REST path traversalSILO 2026-06-18

The articles below are ordered chronologically; incidents investigated on the same day are ordered by CVE number. Dependency-only CVEs remain in the relevant release notes instead of being inflated into application-level incident stories.


CVE-2026-32285: The jsonparser Advisory That Required No Patch

A security investigation that ended without a code change: the resolved dependency already contained the fix, and reachability analysis found no vulnerable path.

Status: Closed without a code change
GitHub issue: pgsty/minio#26

Security maintenance is not always a sequence of “find a vulnerability, then ship a patch.” The initial assessment of CVE-2026-32285 was that the repository might still carry a vulnerable jsonparser; replacing the dependency or maintaining another fork was even considered. Checking the resolved module version and actual reachability changed the conclusion: the tree already used v1.1.2, which contained the fix, and govulncheck found no reachable vulnerable symbol.

The right final action was not to manufacture an upgrade. It was to record the evidence and close the issue.

What was wrong with the initial assumption

The issue was first understood as “this dependency has no fixed version.” Acting on that premise without verification could have produced several changes that looked proactive but made the project worse:

  • changing the dependency graph for no security benefit;
  • introducing compatibility regressions in the name of a nonexistent fix;
  • adding another fork that would need long-term maintenance;
  • implying that previous SILO releases were demonstrably exposed when that had not been established.

Security work cannot be measured by whether it produces a diff. Leaving correct code unchanged is itself a security decision, and it needs evidence.

Investigation

The investigation narrowed the question through four layers of evidence:

  1. Confirm the version actually selected in the current go.mod and go.sum graph.
  2. Check the upstream release and establish that v1.1.2 already contained the relevant fix.
  3. Run and inspect govulncheck; it reported no reachable vulnerable symbol.
  4. Attribute the discrepancy in the issue to stale advisory or vulnerability-database information, not to a vulnerability still present in the source tree.

Four claims must remain separate: a version was once listed as affected, a package is imported, a vulnerable symbol is reachable in the program, and remote input can actually exploit that path. None of them proves the others.

Why there was no “just in case” upgrade

If the selected version already includes the fix, bumping to an arbitrary newer version does not make the system safer. It only expands the change surface and makes later regressions harder to attribute. That is especially risky in a large Go module graph.

The final decision was therefore to:

  • avoid committing a fictitious fix;
  • preserve the version and reachability evidence in the issue;
  • keep version gates and govulncheck in place to detect a future dependency rollback;
  • treat “no change required now” as a dated conclusion, not a permanent exemption.

Verification boundary

This incident established that the checkout examined on 2026-04-15 did not require a code change for CVE-2026-32285. It does not establish that every future branch, module graph, or release will remain unaffected. A dependency downgrade or a change in module selection requires the version and reachability checks to be repeated.

This article records the investigation and the basis for closure. The original govulncheck was not rerun while preparing this chronicle.

The principle this incident left behind

The objective of security maintenance is an accurate risk conclusion, not a patch for every issue. For a dependency CVE, ask in order: which version is actually selected, whether the vulnerable code enters the program, whether the symbol is reachable, and whether a deployed entry point makes it exploitable. Only when those answers require a source change should the investigation produce a diff.

CVE-2026-33322: OIDC JWT Algorithm Confusion

The OIDC verifier mixed a client secret with JWKS keys; restoring asymmetric, JWKS-only verification closed the algorithm-confusion path.

Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
Affected entry points: AssumeRoleWithWebIdentity, AssumeRoleWithClientGrants
GitHub issue: pgsty/minio#22

The old implementation placed the OIDC client secret in the JWT verifier keyring while also accepting HMAC signing methods. An attacker who knew that client secret could therefore mint an HS-signed token and exchange it through STS for temporary credentials. The final fix restored asymmetric, JWKS-only verification. It deliberately broke HS256/384/512 compatibility instead of keeping an option that would reintroduce ambiguous trust semantics.

The vulnerability was not the absence of signature verification

At first glance, the old code did verify JWT signatures. The boundary that failed was more specific: which kind of key the verifier would accept, and whether the token header could select an algorithm with semantics that did not match that key’s intended role.

The attack chain required several conditions:

  • the attacker obtained the OIDC client secret;
  • the attacker constructed an HMAC-signed ID token;
  • the verifier treated the client secret as an HMAC signing key;
  • the token reached the WebIdentity or ClientGrants STS flow and was exchanged for temporary credentials.

Disclosure of a client secret is already serious, but it should not automatically confer the power to issue arbitrary user ID tokens. Combining those capabilities in one keyring created the algorithm-confusion vulnerability.

A compatibility path was implemented, then deliberately removed

During the fix, an allow_hmac-style compatibility path was implemented. It appeared reasonable: keep the secure default while letting users with a real requirement opt in. But retaining a shared secret in the general verifier keyring meant administrators would need to understand that the option expanded the entire STS trust boundary. Any future drift in the method allowlist could reopen the flaw.

The trade-off became clear:

OptionBenefitRiskDecision
Keep the secret keyring and restrict some algorithmsSmall change; preserves HMAC IdPsThe keyring still mixes two trust semanticsRejected
Add an allow_hmac optionMakes compatibility explicitThe option is difficult to reason about correctly and expands the test surfaceImplemented, then reverted
JWKS-only verificationClear boundary; refresh and retry use the same parserHS users must migrateAccepted

The most important decision was not what code was added, but that a completed compatibility implementation was removed.

Final invariants

The fix was concentrated in the OIDC JWT verification path and established four rules:

  • verifier keys come only from the identity provider’s JWKS;
  • the OIDC client secret never enters the JWT verification keyring;
  • HS256, HS384, and HS512 are always rejected;
  • the ordinary RS256 flow and JWKS refresh/retry use the same method allowlist.

The CVE was not used as a pretext for expanding JOSE support. PS256 and EdDSA remained out of scope.

Verification and release

The development record includes HS256 rejection, RS256 acceptance, JWKS refresh/retry regression tests, and focused go test ./internal/config/identity/openid. Temporary compatibility helpers, configuration, and tests were all removed from the final diff.

The public fix is f1f2239, released with SILO 2026-04-17. This article records the historical verification; those tests were not rerun while preparing the chronicle.

Compatibility cost

This is an explicit breaking change. Identity providers that still issue HS256/384/512 tokens must migrate to JWKS-backed RSA or ECDSA before upgrading SILO. The project chose a narrower trust model that is easier to explain and audit over preserving a configuration that happened to work before.

CVE-2026-33419: LDAP STS Enumeration and the Throttling Chain

From uniform authentication failures to corrected refunds, proxy attribution, and account lockout: LDAP STS hardening through two rounds of counter-fixes.

Status: Released, followed by two rounds of corrections
First containing release: RELEASE.2026-04-17T00-00-00Z
Complete correction: RELEASE.2026-06-18T00-00-00Z
GitHub issue: pgsty/minio#23

The core vulnerability was straightforward: LDAP STS returned different results for “user does not exist” and “password is wrong,” creating a username oracle. The first fix unified the external authentication failure and added limits by source IP and username. Continued review then showed that success refunds, spoofable source headers, reservation accounting, and the shared username bucket could turn the security control itself into a new attack surface.

The final June design removed the username bucket that enabled precise account lockout, retained only the source-IP bucket, and made proxy attribution an explicit deployment contract.

Initial threat model

The entry point is AssumeRoleWithLDAPIdentity. An attacker needs no existing MinIO account. Access to the LDAP STS endpoint is enough to compare the code, status, or message returned for an unknown user and a wrong password, enumerate valid usernames, and combine that knowledge with password spraying, guesses about organizational naming, or social engineering.

The fix could not simply disguise every error as “wrong password.” LDAP connection, lookup-bind, and directory-service failures still needed to appear as infrastructure errors, or operators would lose the ability to diagnose the service.

First round: uniform responses and a limiter

The initial fix on 2026-04-15 did three things:

  • unknown user and bad password returned the same external STS authentication error;
  • LDAP infrastructure failures still returned 500, with the real cause retained in the server log;
  • a new in-memory limiter initially created buckets for both source IP and normalized username.

This closed the content side channel and raised the cost of brute-force attempts, but the limiter state machine and source attribution exposed more problems.

Second round: success, attribution, and accounting

The follow-up changes on April 16 addressed three classes of defects:

  1. Successful authentication must not consume the failure allowance; the reserve/commit/cancel/refund lifecycle had to be explicit.
  2. The socket peer must be used by default; X-Forwarded-For, X-Real-IP, and Forwarded cannot be trusted merely because a request supplies them.
  3. Refund and capacity need hard bounds so cancel logic cannot mint tokens.

A proxy must be placed on an explicit trusted allowlist before it can influence source attribution.

Third round: remove the username bucket

Adversarial review in June overturned the intuition that “source plus username must be stronger than source alone.” A shared username bucket could be exhausted continuously from arbitrary origins. With only a low request rate, an attacker could lock a targeted account before the legitimate user ever reached an LDAP bind.

The final fix therefore:

  • removed the per-username bucket;
  • peeled trusted hops from XFF right to left and selected the first untrusted address;
  • rejected trusted-proxy footguns such as 0.0.0.0/0 and ::/0;
  • stopped using Forwarded for security-sensitive bucketing;
  • allowed X-Real-IP only under a contract in which the proxy overwrites rather than forwards client input.

This turn in the review shows that a security control needs its own threat model. More dimensions of throttling do not automatically mean more security.

Rejected alternatives

OptionWhy it was rejected
Perform a dummy bind for unknown usersAmplifies LDAP load and creates a second, error-prone authentication path after the content side channel is already closed
Bucket all IPv6 clients by /64Legitimate users behind the same site or carrier prefix can throttle one another
Take the leftmost XFF valueClient-controlled and therefore spoofable
Fall back to the peer when XFF and X-Real-IP disagreeAn attacker can create disagreement deliberately and collapse every user behind a proxy into one bucket
Fully support RFC 7239 ForwardedSecurity-sensitive parsing complexity outweighs the practical benefit

Verification and release

The historical record covers limiter reserve/commit/cancel/refund behavior, concurrency, success and infrastructure failures, external equivalence of unknown-user and bad-password responses, and RemoteAddr, spoofed-header, trusted-proxy, multi-hop, and catch-all-CIDR cases. Focused package tests and builds were recorded as passing.

The LDAP security end-to-end test skips when _MINIO_LDAP_TEST_SERVER is absent, so an outer ok cannot be presented as proof of the full LDAP scenario.

The first public fix was 6619d0c. Follow-up corrections include c55b52c, 817a457, 084a154, and 5e40665.

Final cost and residual risk

  • The limiter now keys only on source IP and gives up a hard per-account throttle across different origins.
  • It is per-node and in-memory, not a cluster-wide password defense.
  • Botnets, distributed origins, IPv6 address rotation, and LDAP bind timing remain.
  • Incorrect trusted-proxy configuration can still destroy source attribution.
  • A Forwarded-only deployment falls back to the peer bucket and loses granularity.

Rate limiting can reduce attempts from one source. The uniform external authentication response is what actually conceals whether a username exists.

CVE-2026-34204: Replication Metadata Injection

Ordinary PUT and COPY requests could forge internal replication state; the fix restores replication-only metadata solely inside authorized replication paths.

Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
GitHub issue: pgsty/minio#24

Ordinary PUT and COPY requests could smuggle X-Minio-Replication-* headers into internal X-Minio-Internal-* SSE metadata, creating objects whose replication state did not match the authorized path and could even make them unreadable. The final fix stopped accepting replication-only metadata by default, restored it only in a trusted flow authorized for ReplicateObjectAction, and sanitized CopyObject before any header consumer ran.

Threat model

An attacker needed only ordinary object-write permission, not internode credentials. The input came entirely from client-controlled X-Minio-Replication-* headers, but metadata extraction converted it into internal replication or SSE state.

Later read paths interpreted the object according to that false internal state. The result could be an unreadable object: an integrity and availability failure. Almost every production server accepting untrusted writes needed to be treated as affected.

The root problem was not the header name. It was that data from an untrusted source acquired internal semantics without passing replication authorization.

Reject the whole request, or sanitize precisely?

Rejecting an ordinary request whenever it contains a replication header is the most obvious fix. It would also turn a header clients were previously allowed to send and have ignored into a hard failure. The final design was more precise:

  • the default extraction path does not accept replication-only metadata;
  • ordinary PUT and COPY strip those fields first;
  • only a path authorized for ReplicateObjectAction restores them;
  • replica-status writes use the same trusted condition;
  • legitimate multipart and Snowball replication flows explicitly restore the SSE metadata they require.

That keeps the compatibility change inside internal semantics instead of expanding it to every client carrying an extraneous header.

Why CopyObject had to sanitize early

CopyObject headers are not used only for the final metadata map. They can be consumed earlier by precondition logic and SSE-C source handling. Removing them immediately before the object write is too late: earlier consumers have already been contaminated.

The final sanitization occurs before those consumers. “Untrusted replication headers never enter internal semantics” becomes one invariant instead of a convention every downstream function must remember to enforce.

Implementation and verification

The change covered handler utilities, object handlers, and multipart handlers, with tests at several layers:

  • trusted and untrusted metadata extraction at the helper layer;
  • malicious PUT and COPY cases at the handler layer;
  • CopyObject header sanitization;
  • red/green comparison between the vulnerable parent and the patched tree;
  • live-server before/after behavior showing that a malicious header no longer made an object unreadable;
  • continued operation of legitimate replication, multipart, and Snowball flows.

The public fix is fcb8f24. This article preserves the historical verification boundary; no live server was started again while preparing the chronicle.

Cost and residual risk

  • Internal replication headers supplied by ordinary clients are now ignored or stripped.
  • Replication-only metadata must be restored explicitly inside an authorized branch.
  • If a future replication entry point forgets to restore it, the result should be a functional regression rather than another untrusted write path.
  • The audit focused on replication headers; it does not establish that every X-Minio-Internal-* field has undergone the same trust review.

This incident leaves a simple review question: a field that looks “internal” is not necessarily trusted. Ask where it came from and which authorization decision allowed it to acquire internal meaning.

CVE-2026-39414: Oversized S3 Select Records and a SIMD Bypass

The first fix imposed a 1 MiB bound on CSV and JSON Lines; the second found that the SIMD fast path bypassed it completely.

Status: Released; the second-round fix was completed in June
Initial fix release: RELEASE.2026-04-17T00-00-00Z
Complete fix release: RELEASE.2026-06-18T00-00-00Z
GitHub issue: pgsty/minio#25

The first fix in April reused the existing 1 MiB maxCharsPerRecord limit for both CSV and ordinary JSON Lines. This prevented unbounded buffering while waiting for a delimiter and returned the explicit OverMaxRecordSize error to clients. A June review then found that CPUs with SIMD support took a different simdjson fast path that bypassed the limit completely.

The final solution sent JSON Lines through the bounded reader on every CPU. The same review also corrected error mapping, parser errors, and the flush of completed records before a terminal error. SILO temporarily gave up the SIMD fast path in exchange for consistent security semantics.

Threat model

An attacker can submit or query an object containing an extremely long single record. The reader continues buffering until it sees a record delimiter, allowing memory and CPU denial of service. More subtly, the same input can select a different implementation according to the machine’s CPU features. Safe behavior on a test machine does not necessarily prove safe behavior in production.

Error semantics are part of the fix. If an oversized record appears only as a generic InternalError, clients and monitoring systems cannot distinguish an enforced security limit from a server failure.

First round: reuse the existing 1 MiB invariant

The first patch did not invent a new configuration knob. It applied the existing maxCharsPerRecord = 1 MiB rule:

  • the CSV splitter and line-delimited JSON rejected oversized records before buffering or parsing them further;
  • the earliest splitter error was preserved instead of being overwritten by a partial decode;
  • the error propagated as OverMaxRecordSize rather than collapsing into InternalError.

This was a deliberate compatibility contraction. Clients with lines or records larger than 1 MiB now had to split their input.

Second round: a hardware-dependent bypass

Following the call chain again in June exposed this path:

JSON Lines -> simdj.NewReader -> simdjson.ParseNDStream

When simdjson.SupportedCPU() returned true, JSON Lines bypassed the bounded json.PReader. The third-party parser kept reading past a chunk boundary until it found a newline. A generic reader wrapper could not simultaneously preserve already completed records and guarantee a bound on the next record.

The final choice was not another wrapper. JSON Lines temporarily stopped using the SIMD path and always used the bounded PReader. If SIMD support returns, that implementation must enforce the same record limit itself and pass the same CPU-independent regression suite.

Stream semantics corrected in the same round

The review also fixed several adjacent behaviors:

  • use errors.As to pass through errors implementing SelectError, not just one concrete type;
  • have the JSON worker wrap parser failures as JSONParsingError;
  • flush completed records still waiting below the batch threshold before emitting a terminal error event;
  • preserve error priority in input order instead of letting a later oversized record overwrite an earlier parse error.

Those details determine whether a client sees the correct failure or a resource-limit fix that quietly broke the streaming protocol.

Deliberately left outside this CVE

  • The historical mismatch between CSV AllowQuotedRecordDelimiter and the outer physical-newline splitter.
  • Whether \r in CRLF counts toward the record length.
  • Restoring SIMD performance without an equivalent bound.

These questions may be real, but they require independent AWS-compatibility evidence or a more complex quote-aware splitter. They did not belong in a security patch based on guesses.

Verification and release

The historical record includes oversized JSON Lines, error-code preservation, and behavior tests that do not depend on the local machine’s SIMD capabilities. go test ./internal/s3select/... -count=1 and git diff --check were recorded as passing.

The initial public fix was c5765dc; the complete June correction is fd69c89. Those tests were not rerun while preparing this article.

Final trade-offs

  • JSON Lines performance may decrease; this incident did not produce a benchmark that quantifies it.
  • The 1 MiB per-record limit rejects oversized input accepted by previous releases.
  • Quoted, multiline CSV semantics still need separate work.
  • Any future CPU-specific fast path must share the slow path’s security tests.

The second fix leaves the central lesson: a security invariant must hold across hardware-dependent paths. A green test on one CPU does not prove that another execution engine is protected.

CVE-2026-40344: Snowball Auto-Extract Authentication Bypass

A Snowball unsigned-trailer request could reach the extractor before authentication; the fix verifies SigV4 before any tar byte crosses that boundary.

Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
GitHub advisory: GHSA-9c4q-hq6p-c237

Snowball’s PutObjectExtractHandler omitted the streaming unsigned-trailer authentication case. A tar stream with a forged signature could enter untar() before authentication completed, and one request could fan out into many object writes. The final fix initialized the correct reader, handled the decoded length, and completed SigV4 verification before any tar byte reached the extractor.

Why the identifier changed

The official CVE had not been assigned when the fix was written, so the commit subject used the temporary identifier fake CVE-2026-40028. The final identifier is CVE-2026-40344. The historical commit was not rewritten; the advisory and this article use the official number.

From one missing authentication case to bulk object writes

The entry point was Snowball / PutObjectExtract auto-extraction. The request used unsigned-trailer streaming, an authentication type the old handler did not cover as ordinary PUT did.

The danger was larger than one incorrectly authorized request. Once the tar stream entered untar(), that request could create multiple attacker-chosen objects. An authentication omission was therefore amplified into a bulk-write problem.

Final invariant: the extractor sees zero bytes on failure

The key statement in the fix was:

If authentication ultimately fails, untar() must have seen zero bytes.

That rule excludes “extract first, then roll back if authentication fails.” Object writes travel through several paths, and proving a complete rollback is much harder than proving that input never crossed the boundary. Authentication had to close before data entered the extractor.

Implementation

The final change:

  • recognized authTypeStreamingUnsignedTrailer;
  • read X-Amz-Decoded-Content-Length;
  • used newUnsignedV4ChunkedReader();
  • performed complete SigV4 request verification before entering untar();
  • preserved valid signed Snowball requests and CRC32 trailer flows.

Verification

The historical commit and investigation record cover:

  • rejection of a forged-signature Snowball unsigned-trailer request;
  • rejection of anonymous Snowball writes to a non-public bucket;
  • successful extraction with a valid signature and trailing CRC32;
  • red/green comparison between the vulnerable parent and the patched tree;
  • containerized before/after smoke tests.

The public fix is b50ab58. The container tests were not rerun while preparing this article.

Compatibility and residual risk

  • Clients that relied on an unsigned-trailer Snowball combination whose signature was never really verified will fail after upgrading.
  • Authentication now closes before extraction, but tar paths, archive-size limits, and object-count limits remain separate security surfaces.
  • Snowball and ordinary unsigned-trailer requests now share a reader; future changes must regress both paths together.

The essence of the fix was not another if. It moved the authentication decision in front of the actual amplification boundary.

CVE-2026-41145: Unsigned-Trailer Query Authentication Bypass

Query-string SigV4 credentials entered the unsigned-trailer stream without signature verification; the shared reader now closes that boundary once for every caller.

Status: Released
First containing release: RELEASE.2026-04-17T00-00-00Z
GitHub advisory: GHSA-hv4r-mvr4-25vw

Query-string SigV4 credentials could enter a STREAMING-UNSIGNED-PAYLOAD-TRAILER data flow, while the old code verified the signature only when an Authorization header was present. A request carrying a valid access-key identifier could therefore complete a write without a correct signature.

The final fix moved presigned rejection and SigV4 verification into newUnsignedV4ChunkedReader(), making every caller consuming that stream share one authentication boundary.

Identifier note

The official CVE had not been assigned when the patch was written, so its commit subject used fake CVE-2026-40027. The final identifier is CVE-2026-41145. The historical commit remains unchanged; public material uses the official identifier.

Root cause: authentication was coupled to transport form

The affected entry points included PutObject and PutObjectPart. The request selected STREAMING-UNSIGNED-PAYLOAD-TRAILER, with its credentials and signature in the query string rather than the Authorization header.

The old handler used header presence to decide whether to verify a signature. The body reader still consumed the data normally, silently degrading query authentication into something close to an anonymous write. The attacker needed to know a valid access-key identifier, but did not need to produce a correct signature.

The problem was not failure to parse the query parameters. It was that authentication depended on how credentials were transported instead of the trust boundary at which the stream was consumed.

Why the patch did not live in each handler

OptionRiskDecision
Add header/query checks separately to PutObject and PutObjectPartCloses today’s entry points, but a new caller can omit the check againRejected
Invent a compatible presigned unsigned-trailer protocolGreatly expands protocol and test surface without an existing support contractRejected
Reject and verify centrally in newUnsignedV4ChunkedReader()Forces every consumer through the same boundaryAccepted

Anonymous unsigned-trailer requests were not prohibited wholesale. If bucket policy explicitly permits anonymous writes, they can still follow the anonymous authorization path. The forbidden state is the mixture of query credentials with no verification of those credentials.

Implementation and verification

The fix performs presigned rejection and SigV4 verification at the reader entry in cmd/streaming-v4-unsigned.go, while removing the gates in the PutObject and multipart handlers that depended on header presence.

New tests cover forged query PUT, multipart, mixed authentication, and anonymous policy. The historical record also includes a vulnerable-parent write that succeeded, failure on the patched tree, and live-server before/after smoke tests showing that header-authenticated and valid anonymous flows continued to work.

The public fix is fa7c579. The live exploit was not rerun while preparing this article.

Compatibility and residual risk

  • Presigned/query unsigned-trailer is now explicitly unsupported, an intentional breaking change.
  • Moving the fix into the reader significantly reduces the chance that a sibling handler omits the check again.
  • Other streaming authentication modes still need their own audits; this reader fix does not establish that every SigV4 streaming combination is safe.

The shape of this fix matters as much as its payload: when several handlers share an authenticated data stream, authentication belongs to the reader rather than to optional checks in each caller.

CVE-2026-42600: ReadMultiple Storage-REST Path Traversal

From complete preflight validation to deleting the API: why an internal file-reading endpoint with no production callers was not worth retaining.

Status: Released
First containing release: RELEASE.2026-06-18T00-00-00Z
GitHub advisory: GHSA-xh8f-g2qw-gcm7
Affected scope: Distributed erasure only; cluster-root / internode JWT required

The msgpack body of /rmpl carried Bucket, Prefix, and Files. The old code joined those values into filesystem paths without a containment check. The initial fix implemented full preflight validation. Continued call-chain review then found that this API had had no production caller since 2024. The final solution changed from “retain and harden” to removing the route, handler, client, interface, and generated code.

Deleting roughly a thousand lines was a larger source diff than a local validation guard, but it left a smaller long-term attack surface.

Threat model

The vulnerable route was registered only in distributed erasure mode; single-node deployments were unaffected. An attacker needed an internode JWT derived from the root secret, control of a node, or the ability to intercept unencrypted traffic between nodes.

The dangerous fields were inside the msgpack body, not the URL or form data, so upper HTTP path middleware never saw them. xlStorage.ReadMultiple joined and read the resulting paths directly, allowing them to escape the drive root.

This was not an anonymous S3 vulnerability. It crossed the boundary from “cluster root or peer” to “any file readable by the node process.”

First design: retain the API and validate it completely

The initial patch in xlStorage.ReadMultiple:

  • rejected absolute paths, . and .. segments, backslashes, Windows drive prefixes, and NUL bytes;
  • checked final containment across drive, volume, prefix, and file;
  • preserved the historical contract for an empty Bucket and .minio.sys/multipart where possible;
  • returned an error before any read or streaming began.

That design could close the known traversal, but review quickly exposed an early-return gap.

MaxResults exposed the danger of validate-as-you-use

The first patch validated each Files item inside the read loop. For Files=[good, bad] with MaxResults=1, the function returned after reading the first item and never validated the second.

It did not read the second malicious file, but it violated the intended invariant that the entire msgpack request must be valid before execution. Validation was therefore moved into a complete preflight pass, with path-length checks also completed before streaming.

The general rule is useful beyond this endpoint: when a request can return early or stream a partial response, checking an element immediately before use is not equivalent to validating the whole request.

Final decision: delete the API

Further call-chain audit established that:

  • upstream removed the last production caller in September 2024;
  • multipart had moved to ReadParts;
  • the current tree had no in-tree production consumer;
  • upstream’s final remediation also deleted ReadMultiple.

The final change removed the route, handler, client wrapper, StorageAPI / xlStorage method, metric, datatype, and generated code. storageRESTVersion retained the existing compatibility strategy.

OptionShort-term changeLong-term maintenance surfaceDecision
Validate in placeSmaller diff and preserved endpointPermanently retains an unused, privileged file-reading APIAbandoned
Delete the APIRemoves more interface and generated codeMinimizes attack and maintenance surfaceAccepted

Verification and release

The in-place validation phase ran focused tests for xlStorage, the storage-REST client, msgpack encode/decode, and path edge cases; adversarial review exposed the MaxResults flaw. The deletion phase checked the route, client, interface, generated surfaces, and absence of callers.

The public fix is 73ac524, released with SILO 2026-06-18. The post-deletion full suite was not rerun while preparing this article.

Compatibility and claim boundary

  • The external S3 API is unchanged.
  • Third-party implementations that privately called the internal /rmpl endpoint will stop working.
  • A mixed-version rolling upgrade may encounter a protocol mismatch, so cluster nodes should be kept on the same version during the upgrade.
  • Removing this endpoint proves only that ReadMultiple no longer exists; it does not establish that every internal node request carrying body paths has completed a containment audit.

This CVE reached the right final fix, but it also leaves an important distinction: closing one endpoint is not the same claim as closing an entire vulnerability class.