Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

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.

1 - Silo Console 2.1.0 Released

A bilingual console: a zero-dependency English/Chinese interface across every screen, the dashboard migrated to MinIO Metrics V3 with explicit zero-state semantics, and a batch of correctness fixes including escape-proof placeholders and a select-all that cannot lie.

Published: 2026-08-06 · Version: v2.1.0 · Repository: pgsty/silo-console

SILO Console 2.1.0 is the first feature release after the independent 2.0.0. It does three things:

  1. Speaks two languages — every console screen, help topic, and documentation link now renders in English or Chinese, behind a toggle on every page, with zero new runtime dependencies;
  2. Reads the right metrics — the dashboard moves off the MinIO Metrics V2 names onto V3, with explicit handling for the semantics V3 changed underneath it;
  3. Stops lying in edge cases — a select-all that matched what a bulk action would delete, placeholders that survive object names containing $&, timestamps that carry a timezone, and empty metrics that read “no data” instead of a fabricated 0.

This is a minor release. No environment variable, module path, API contract, binary name, or data layout changes. Upgrading is a binary or image swap.

Note

A 2.1.1 patch follows this release

v2.1.1, published the same day, completes the legend hardening described below: a label placeholder the legend builder cannot resolve is now removed instead of leaking literal braces into the Traffic chart legends, the one remaining substitution branch is escape-proofed against label values containing $& or $1, and the License page reports the actual release version instead of 2.0.0. Nothing else changes — upgrade straight to 2.1.1, and everything in this note applies unchanged.

Note

Rebuild your embedded assets if you vendor this console

2.1.0 fixes a packaging defect present on the main branch after 2.0.0: the go:embed payload still carried the 2.0.0 frontend build, so a binary built from an intermediate commit would serve the old UI. The released 2.1.0 artifacts are built from the regenerated payload and are unaffected.

A Bilingual Console

The console is an administration surface for an object store, and a large share of its operators read Chinese first. 2.1.0 makes the interface bilingual without importing an i18n framework — the embedded delivery model means every kilobyte is paid for in the binary. This is issue #6, which proposed i18next; the dependency-free substitution is the one deliberate deviation from it.

How it works

The design constraint was: no new dependency, no build step, no extraction pipeline, and partial coverage must never break the page.

  • English source strings are the dictionary keys. t("Create Bucket") looks up the Chinese entry; a missing key returns the English string unchanged. Coverage can therefore grow incrementally, and a typo degrades to English rather than to a raw key like console.bucket.create.
  • Three dictionaries, one merge. zh.ts (165 chrome entries), zhHelp.ts (247 help-topic entries), and zhScreens.ts (1,373 screen entries) merge with chrome taking precedence — about 1,785 entries in total.
  • The language preference mirrors dark mode: localStoragesystemSlicesetLanguage. There is no browser-locale detection; the default is English, and the choice is explicit.
  • Central interception points rather than per-callsite edits: the page-header wrapper, confirm dialogs, help items, route definitions, and the dashboard’s panel renderer each translate on the way out. This is why 220 screen files could be localized without touching their business logic.
  • Module split matters. i18n/lang.ts holds pure primitives (translate, localizeUrl) and imports no store — systemSlice depends on it, so importing the store back would form a cycle. The hooks (useT, useLanguage, useLocalizedLink) and interpolate() live in i18n/index.tsx.

The toggle is a stroke-drawn 文/A icon mounted in the page header on every page and reused on the login page.

What it covers

Login and SSO flows, navigation and the command palette, the dashboard and every metrics panel, buckets and the full object browser (uploads, previews, sharing, versioning, rewind), users/groups/policies/access keys, configuration and event destinations, IDP and KMS, logs, health reports, speedtest, profiling, inspect, trace, watch, and the license page.

Beyond visible strings:

  • Documentation links localize. silo.pgsty.com links gain a /zh prefix in Chinese; the Pigsty site swaps domains (pigsty.iopigsty.cc). GitHub, MinIO, AWS, and YouTube links are left alone.
  • The help blog feed is per-language, fetching /zh/blog/index.xml in Chinese, with an independent cache per language.
  • The command palette stays searchable in both languages. Menu entries translate for display but keep their English originals as keywords, so “桶” and “buckets” both match.
  • Chart legends translate only their static prefix. translateLegend preserves instance suffixes like [server:drive], and the data layer keeps raw legends so components that match on them for arithmetic (capacity summing) keep working.
  • Timestamps are unified, not merely translated — see below.

What it costs

Roughly +61 KB on the embedded payload (2.79 MB → 2.85 MB, +2.2%), zero new dependencies, and the dictionaries land in their own lazily-loaded chunk. The English rendering path is byte-stable: with the default language, output is identical to 2.0.0.

What stays English

Backend error strings (182 of them) are produced by the Go server and are not translatable from the frontend. A handful of strings hardcoded inside the vendored mds component library — the collapsed-menu “Sign Out” tooltip, and the data table’s “Columns”, “Loading…”, and ON/OFF toggles — remain English; two of them (“Sign Out”, “Actions:”) are swapped via a scoped CSS rule, but the rest would require patching the vendor.

Metrics V3 Migration

The dashboard queried MinIO Metrics V2 names. SILO deployments scrape V3 (/minio/metrics/v3), so the dashboard depended on an endpoint the monitoring pipeline no longer collected. 2.1.0 rewrites all 26 widgets onto the V3 catalog — 31 queries over 29 distinct metric names — and drops three widgets (51/61/62) that no layout ever referenced. This is issue #7; the Info-page half is #8.

The decision is V3-only: no runtime fallback, no probing, no version-selection knob. SILO Console targets SILO deployments, where the server, the scrape pipeline, and the console ship together. The SILO server keeps serving V2 endpoints for external consumers; the console simply stopped using them. A fallback would have been actively harmful — a metrics store retaining 15 days of V2 series would let an or-fallback silently read stale data.

The semantics V3 changed

Three properties of V3 break a naive name-for-name rewrite, and each needed a deliberate answer:

  1. Cluster groups are exported identically by every node. /cluster/* metrics carry no server label and are not leader-gated, so an N-node scrape yields N duplicate series. Queries aggregate with max()/min() — never sum(), which would multiply cluster totals by the node count.
  2. Zero values are not exported at all. Any metric whose value is ≤ 0 is skipped. Offline drive counts, healing-drive counts, and erasure-set health simply vanish rather than reporting 0, which a stat card renders as an empty panel. Every affected query carries a companion guard so the panel reads a real 0.
  3. There is no minio_heal_* namespace. The V2 heal activity signal was in-memory anyway — it reset on restart and bumped on any scan. It is replaced by two cards with defensible semantics: Erasure Health (baselined on write quorum) and Usage Data Age (how stale the scanner’s usage snapshot is).

Zero-state semantics

An adversarial review of the migration produced eight findings, all fixed before release. They share one theme — the difference between zero, no data, and not yet scanned:

  • Capacity free/used baselines on the always-present total, so a full cluster reads 0 free instead of vanishing.
  • Online Drives is guarded against the all-offline case, where the zero-skip would erase the panel exactly when it matters most.
  • Bucket and object counts guard on the usage group’s own freshness gauge, so a cluster that has not completed its first scan reads no data rather than a fabricated 0.
  • Empty single-value results render as , not 0.
  • An empty size distribution no longer fabricates seven zero-height bins.
  • Fractional rates stay visible (parseFloat axis domain, two-decimal CPU formatter) instead of collapsing to 0.
  • Sub-second Usage Data Age clamps to “1 second” instead of rendering blank.

A regression suite (api/admin_info_metrics_test.go) now pins every widget query to the V3 catalog, asserts widget-ID uniqueness, and enforces the per-widget guard taxonomy: health and traffic widgets need a nodes-online companion, usage counts need the usage-group freshness companion, and capacity needs the total baseline. The full mapping is documented in docs/metrics-v3.md.

Also fixed

  • Widget 17 queried sent_bytes twice and widget 11 queried syscall_read twice — both internode/syscall pairs were transposed into duplicates.
  • Label-less matrices (the result of max() aggregation) serialize with no metric field at all, which crashed the frontend’s label extraction and produced a 0 B capacity donut and an empty usage-growth chart. Guarded.
  • An unused per-widget Prometheus label-values prefetch stalled every widget request by up to a second. Deleted.
  • The dashboard’s usage cards, chart controls, and dense Traffic/Resources panels were rebuilt on one grammar and now reflow through tablet widths.

Two server-side bugs were identified during this work and are tracked upstream rather than worked around here: minio_cluster_usage_buckets_since_last_update_seconds emits nanoseconds (the objects variant is correct), and V3 bucket-level sent/received traffic are transposed.

Correctness Fixes

Placeholders that survive real object names

String.prototype.replace interprets $&, $', $`, and $1 in the replacement value as directives. S3 keys legally contain $. So an object named report$&.csv did not render as itself — it re-injected the matched placeholder text into the output and corrupted the message. All 37 dictionary placeholder substitutions now pass the value through a function replacement, where no such interpretation happens. This was a latent bug in the original English UI, not something i18n introduced; the i18n audit is simply what found it.

A select-all that means what it shows

The vendored data table renders a plain untranslatable “Select” header whenever onSelectAll is absent — which was the case on all seven selectable tables. Worse, the naive fix is wrong: a select-all that replaces the whole selection drops rows hidden by an active filter, so the header checkbox and a subsequent bulk action can target different sets. The implementation toggles only the currently visible rows and preserves filter-hidden selections, so the header state can no longer imply a different set than the action would touch.

Timestamps with a timezone

Bucket, object, version, rewind, and access-key timestamps rendered as a mix of verbose English forms and — in several places — a 12-hour clock without AM/PM, which is simply ambiguous. All of them now render as yyyy-MM-dd HH:mm[:ss] (ZZZZ) in both languages.

A translation runtime that survives live data

t() also receives runtime strings: user agents, RSS titles, object names. Two hardening changes followed:

  • misses return unchanged, unconditionally — the implicit @context suffix stripping is gone, because it silently mutated live data that happened to contain @;
  • dictionary lookups are guarded with hasOwnProperty, so a hostile input naming an inherited Object.prototype member (constructor, toString) cannot leak a function into the UI.

Interaction and accessibility

  • An expired session opening a deep link bounced through /login and back, accumulating a redirect chain instead of landing on the form once (#1).
  • Collapsed sidebar buttons carried no accessible name; screen readers announced them as unlabelled (#4). Access Key inputs now declare their autocomplete intent instead of letting password managers guess (#5).
  • Mobile metrics and bucket panels scroll instead of clipping (#3).
  • The speedtest control row wraps instead of overflowing its card, its duration accepts seconds or minutes, and its size defaults to MiB to match its own unit list.
  • Sidebar bucket rows use a virtual row pitch matching the 44px item, so selected and hovered highlights no longer overlap.
  • Unit chips render the selected unit’s label rather than its raw value.

No SUBNET, No Telemetry

Upstream removed Subnet, Registration, and Call Home; this fork inherited that state but still carried three traces. 2.1.0 removes them:

  • the health websocket’s subnetResponse field never addressed a subnet — it is a sentinel meaning “the report was assembled” — and is now reportStatus: "ok";
  • two help topics claimed the health report “uploads automatically to SUBNET” and that inspect output is “transmitted to SILO SUBNET”. Neither was true. They now describe what happens: the report is generated on the deployment and downloaded by the browser;
  • the unreferenced CONSOLE_SUBNET_PROXY constant is deleted.

For the record, 2.1.0’s outbound network posture is unchanged and remains: no analytics, no telemetry, no beacons, no external scripts or fonts. silo-console update is still disabled. The release catalog is contacted only if SILO_RELEASE_SERVICE_HOST (or RELEASE_SERVICE_HOST) is explicitly set — there is no default. The only automatic outbound request the browser makes is the help panel’s blog feed, and only after a user opens the Blog tab.

Upgrade Guide

There is nothing to migrate. No environment variable, module path, protocol field, systemd unit, binary name, or data layout changes between 2.0.0 and 2.1.0.

install -m 0755 silo-console-linux-amd64 /usr/local/bin/silo-console

Two things are worth knowing:

  • The dashboard now requires Metrics V3. If your Prometheus scrapes only the V2 endpoints, dashboard panels will read no-data. Point the scrape at /minio/metrics/v3; Pigsty-managed deployments already do.
  • The language default is English, chosen per browser and stored in localStorage. There is no server-side default and no browser-locale detection, so no existing deployment changes appearance on upgrade.

Verification Scope

Before tagging, the full change set was reviewed and the following gates were run against the final tree: go build, go vet, golangci-lint (0 issues), the Go unit suite across all packages, gofmt, TypeScript type checking, the frontend production build, Prettier across all sources, dictionary duplicate-key checks, and a debug-leftover scan of the complete diff.

The 29 intermediate commits were restructured into 20 logical ones by pure tree operations, and the rebuilt tip was verified byte-identical to the pre-rewrite tree. The embedded payload was rebuilt twice from a clean directory and confirmed byte-identical, which is the property the release pipeline’s zero-diff gate depends on. The pre-rewrite history is retained in a backup ref.

The Metrics V3 migration was additionally reviewed adversarially by an independent model, and all eight findings were fixed (see Zero-state semantics); its queries were validated against a live metrics store with real cluster data.

Known Limitations

  • The SSO end-to-end suite requires an external OpenLDAP/Dex/MinIO topology and was not run in that environment this cycle; the OIDC code paths are covered by unit tests.
  • Backend error strings and several vendored mds component strings remain English (see What stays English).
  • Chinese translation covers the console’s own surfaces; help-topic bodies are translated, but the documentation pages they link to follow the docs site’s own language coverage.
  • Two server-side V3 metric bugs (nanosecond bucket-usage age, transposed bucket traffic) are tracked upstream and are not worked around in the console.
  • Automatic self-update remains disabled; upgrades are explicit.

Issues Closed

2.1.0 closes every issue filed against 2.0.0. Each carries a comment on the tracker describing the fix, the commits, and the coverage added.

Issue Resolution
#1 — unauthenticated deep routes recurse /login Absolute, base-path-aware login destination; deep-link and subpath test coverage
#2 — stale Uptime, malformed legends, cramped menus Uptime derived from real server state, legends resolve on the V3 name label, 32 px chart controls, popup width floors
#3 — 390 px viewport clips content Scrollable metrics tab strip; bucket table with a deliberate mobile column budget
#4 — unnamed collapsed sidebar buttons Labels visually hidden rather than removed from the accessibility tree; named, keyboard-operable collapse toggle
#5 — Access Key fields lack autocomplete metadata Field-level username / new-password tokens in a dedicated autofill section
#6 — English/Chinese localization Hand-rolled bilingual layer, zero new dependencies, English-as-key fallback
#7 — migrate monitoring queries to Metrics V3 V3-only; 26 widgets, 31 queries, 29 metric names, guard taxonomy, regression suite
#8 — replace N/A Info metrics Erasure Health and Usage Data Age, sharing the advanced dashboard’s widget results

Three acceptance criteria are recorded as unmet rather than quietly ticked: web-app has no unit-test runner, so the i18n test suite (#6) and the focused constructLabelNames test (#2) would require introducing test tooling first, and #6’s contributor documentation for adding translation keys is not yet written.

The complete v2.1.0 change set consists of 20 logical commits. The v2.1.0 tag additionally carries three later documentation commits that rewrote the repository README; they change no shipped behavior.

  • 8764f5d — fix(web): stop recursive login redirects
  • 437c56c — fix(ui): make the dashboard and bucket list usable on narrow screens
  • 85fc0c6 — fix(a11y): name collapsed sidebar controls and credential fields
  • e3fed07 — fix(metrics): rebuild dashboard cards, chart controls, and layout
  • fa11576 — feat(login): polish controls and legal attribution
  • 9fc17c1 — feat(i18n): add hand-rolled EN/ZH core, dictionaries, and language toggle
  • 622c02e — feat(i18n): localize login, navigation, and the help system
  • 6a03719 — feat(i18n): localize dashboard and metrics screens
  • 14b1c2d — feat(i18n): localize bucket and object browser screens
  • 0298062 — feat(i18n): localize identity, configuration, and event destinations
  • 41094f6 — feat(i18n): localize observability, admin tools, and shared components
  • e964992 — feat(metrics): migrate the dashboard to MinIO Metrics V3
  • 0b2251f — fix(i18n): harden the translation runtime for live data and chart legends
  • 9b60148 — fix(console): unify timestamps on a timezone-carrying standard format
  • bf110ae — fix(console): give selectable tables a visible-rows select-all
  • 5fc8f22 — fix(i18n): escape-proof all placeholder substitutions
  • fef8fab — fix(console): polish speedtest, sidebar, and help chrome
  • c4911e8 — chore(console): drop SUBNET remnants from health reporting
  • 1d631c4 — docs: record the SILO Console v2.1.0 changelog
  • 912d847 — build: regenerate optimized embedded web assets

Links:

2 - Silo Console 2.0.0 Released

The first independent major release of SILO Console: full identity and delivery migration, a redesigned login page and console UI, embedded assets cut from ~10MB to 3.5MB, zero known dependency vulnerabilities, and a batch of inherited bug fixes.

Published: 2026-08-04 · Version: v2.0.0 · Repository: pgsty/silo-console

SILO Console 2.0.0 is the first major release of this object-storage administration console as an independent project. Continuing from the georgmangold/console v1.9.1 maintenance line, it accomplishes three things:

  1. An independent identity — product name, visual system, documentation entry points, source attribution, and the release pipeline all move into the SILO project, while the Go module path, environment variables, and other compatibility contracts are deliberately retained;
  2. A redesigned interface — the login page, theme system, dashboard, and console details are reworked under one design language, backed by a regenerated brand icon set;
  3. Hardened engineering — the embedded frontend payload shrinks from roughly 10MB to 3.5MB, known dependency vulnerabilities drop to zero, and a batch of inherited defects — including a real runtime data race — is fixed.

Before publication this release went through two independent review passes: a full code review with commit-history restructuring, followed by an adversarial re-verification (exhaustive asset validation, HTTP semantics probing, full routing regression, and smoke tests against the published artifacts themselves).

Warning

Read the compatibility boundary before upgrading

The major-version change in 2.0.0 is about public identity and delivery contracts, not the object data format or the S3 protocol. Installation scripts that reference the old repository, binary name, or container image must be updated; existing integrations that use CONSOLE_MINIO_SERVER, CONSOLE_MINIO_REGION, github.com/minio/console, or the MinIO-compatible Admin API must not be search-and-replaced.

Why 2.0.0

This console originated as MinIO Console and was carried forward by the Alevsk/console and georgmangold/console community maintenance lines. SILO Console continues from there, maintained by the Pigsty community as the browser-based administration interface for SILO.

The version jumps from v1.9.1 to v2.0.0 because these public contracts change together:

  • the product is now uniformly SILO Console, with the primary repository at pgsty/silo-console;
  • the release binary changes from console to silo-console, and the container image moves to ghcr.io/pgsty/silo-console;
  • release assets, checksums, package metadata, CLI descriptions, and project links all switch to SILO;
  • in-product identity, help entry points, copyright attribution, source offers, and trademark notices are re-established.

The migration strategy is “clear external identity, restrained internal compatibility”: operators must take notice, but the underlying compatibility interfaces are not mechanically renamed.

Naming and Delivery Contracts

Scope Previous name or location 2.0.0 contract
Product Console / legacy MinIO Console SILO Console
Repository georgmangold/console pgsty/silo-console
Release binary console silo-console
Container image ghcr.io/georgmangold/console ghcr.io/pgsty/silo-console
Binary assets console-<os>-<arch> silo-console-<os>-<arch>
Checksums console_<version>_checksums.txt silo-console_<version>_checksums.txt
Website and docs upstream / previous maintainer silo.pgsty.com and silo.pgsty.com/docs/

CLI authorship, usage text, and project descriptions now identify Pigsty and SILO Console. DEB/RPM/APK vendor, maintainer, homepage, description, and license metadata are updated accordingly; the executable installs to /usr/local/bin/silo-console.

Deliberately Retained Compatibility Identifiers

The following names still contain minio or the old console, but they are interface, protocol, or installation compatibility layers — not leftover branding:

Surface State in 2.0.0 Reason
Go module github.com/minio/console retained changing it breaks every Go import
Server endpoint CONSOLE_MINIO_SERVER retained widely used by existing deployments
Server region CONSOLE_MINIO_REGION retained existing compatibility contract
Other configuration existing CONSOLE_* variables remain valid avoids migration with no benefit
S3/Admin API names MinIO-compatible fields and enums retained they describe the actual protocol
Development build make console still produces ./console keeps developer workflows working
Package systemd unit minio-console.service retained avoids duplicate services on upgrade
systemd user and config console-user and /etc/default/console avoids unnecessary account/config migration

Upgrade scripts therefore must not run repository-wide minio → silo or console → silo-console replacements. Migrating these compatibility interfaces in the future will require aliases, deprecation windows, and an explicit dual-read strategy; 2.0.0 does none of that.

A Redesigned Interface

2.0.0 is not a logo swap — the interface was redesigned end to end.

Login page

The login page is rewritten from scratch. The left brand panel renders a slowly drifting sine-mesh animation generated purely on Canvas (zero external dependencies, honors prefers-reduced-motion, pauses in background tabs), states the project’s proposition — “Keep the S3 Interface / Own the Object Store” — and keeps the full MinIO trademark notice at the bottom. The right-hand form is functionally untouched, preserving every existing automation selector. The Chakra Petch typeface used by the SILO wordmark ships as a ~20KB locally bundled subset with no external requests.

A unified theme system

All console colors converge into one light/dark theme layer: neutral greys for text and borders, the brand steel blue for primary actions and selection, and a sidebar that uses the same night palette as the login panel in both modes. Controls and cards share consistent radii and transitions, inputs get a keyboard focus ring, and modals animate in (also honoring reduced motion). Server-provided customStyles keep full precedence.

Console polish

  • Dashboard (Metrics): stat cards rebuilt under one grammar — muted labels, tabular numerals, aligned status dots; charts and info strips are theme-driven; the upstream absolute-positioning layout is gone.
  • Unified empty states: placeholder text in Watch, Trace, bucket Events/Replication/Lifecycle, and every other data panel is now centered and de-emphasized instead of raw top-left text.
  • Vertical tabs: detail-page tabs change from bordered grey blocks to a quiet pill list, eliminating the stray empty cell at the bottom of the rail.
  • License page: a new VERSION section shows both the connected server’s release and the Console’s own version; accounts without admin:ServerInfo never issue the request and the row stays hidden. The page also consolidates AGPLv3 licensing, the AGPL section-13 source offer, lineage, and trademark boundaries.
  • A batch of interaction fixes: the sidebar now collapses on initial load at mobile widths (previously it waited for a resize event); the bottom navigation no longer lags window-height changes; the bucket accordion highlight spans the full row; the dashboard no longer overflows horizontally on narrow screens; and the help panel is now truly lazy — the login page makes no external requests at all.

Brand icon set

The favicon, PWA, and Apple Touch icons still carried a previous-generation hand-drawn emblem. 2.0.0 re-rasterizes every size (ico 16+32, favicon 16/32/96, apple 180, manifest 192/512) from the official silo.svg vector emblem, with safe-area margins on home-screen sizes, and trims the Web App Manifest to the modern icon set, dropping the 2014-era legacy density entries. The icon payload drops from 473KB to 160KB, and the browser tab icon finally matches the in-product brand.

Smaller and Faster

Embedded delivery is this console’s core form factor — the frontend ships inside the binary via go:embed. 2.0.0 optimizes that path systematically:

  • Embedded payload: ~9.6MB → 3.5MB. Text assets (JS/CSS/SVG/…) are precompressed at build time with deterministic gzip and embedded compressed-only; legacy WOFF fonts (~1.25MB that no supported browser ever downloads) and a set of entirely unreferenced orphan images are removed.
  • First-load transfer: ~5.7MB → ~1.7MB. Static assets previously shipped uncompressed on the wire; they are now emitted directly with Content-Encoding: gzip at zero runtime cost, with on-the-fly decompression for the rare client that does not accept gzip.
  • Correct HTTP semantics. Accept-Encoding is parsed with full RFC 9110 q-values (gzip;q=0 gets identity bytes), responses carry Vary: Accept-Encoding, and non-GET/HEAD requests to static paths and the SPA entry receive 405 with an Allow header.
  • Reproducible builds. Compression uses a pure-JS implementation (fflate) for byte-identical output across platforms, and the release pipeline enforces a hard gate: rebuilding the embedded assets in a clean environment must produce zero diff against the commit.

Release binaries (all frontend assets included, stripped) weigh roughly 35–40MB; for the downstream SILO server, embedding this console now costs about 3.5MB instead of about 10MB.

Security and Dependencies

Go: the build baseline moves to Go 1.26.5 and the golang.org/x family is fully refreshed. Every reachable vulnerability reported by govulncheck is resolved:

Dependency Fixed version Advisories
google.golang.org/grpc v1.82.1 GO-2026-6061
github.com/prometheus/prometheus v0.311.3 GO-2026-5710 / -5662 / -5381 / -5264 (incl. remote-read DoS)
github.com/klauspost/compress v1.18.7 GO-2026-5841

The single remaining advisory sits in golang.org/x/crypto, has no upstream fix yet, and is unreachable from this codebase; it is tracked as a known item.

Frontend: the full dependency-tree audit (production and tooling) is clean, covering the high-severity form-data CRLF injection and the DOMPurify and qs advisories; React Router is migrated to 7.18.2 (keeping the v6-compatible declarative API, with full routing regression). The only explicitly ignored advisory affects an unstable API this project does not use.

Runtime correctness: a real data race between HTTP log-target initialization and shutdown is fixed, along with shared-mock races in the test suite; supported Go packages pass -race across the board. As a side benefit, the go-m1cpu upgrade fixes the local go run cgo crash on recent macOS.

Update Checks and Default Network Behavior

This release keeps conservative defaults for upgrade tooling:

  • automatic self-update in silo-console update is disabled — the command prints guidance and never downloads or replaces the binary;
  • the release catalog gains SILO_RELEASE_SERVICE_HOST, with the previous RELEASE_SERVICE_HOST as a compatibility fallback; with neither set, no remote release service is contacted;
  • the help panel’s blog content loads only when opened, and its links accept https://silo.pgsty.com exclusively.

Automatic updates will be reconsidered once signed release assets and a tested rollback path are in place.

Release Artifacts and Platform Matrix

The release ships 16 assets:

Type Coverage
Standalone binary Linux amd64/arm64/arm, macOS amd64/arm64, Windows amd64
System packages DEB / RPM / APK × amd64/arm64/armv6
Checksums silo-console_2.0.0_checksums.txt (SHA-256)

The pipeline triggers on tag pushes, pins third-party Actions to commit SHAs, and enforces the clean-checkout and zero-diff asset-rebuild gates before GoReleaser runs.

Upgrade Guide

Standalone binary

install -m 0755 silo-console-linux-amd64 /usr/local/bin/silo-console
/usr/local/bin/silo-console server

When building from source, make console still produces ./console; install it under the release name before wiring it into a production service.

DEB/RPM/APK and systemd

Packages continue to install /etc/systemd/system/minio-console.service, whose unit starts /usr/local/bin/silo-console. EnvironmentFile=/etc/default/console, console-user, and existing CONSOLE_* variables are unchanged. This retention lets package upgrades keep acting on the existing service instead of creating a parallel one.

Configuration and integrations

  • do not rename CONSOLE_MINIO_SERVER or CONSOLE_MINIO_REGION;
  • do not touch github.com/minio/console in Go imports;
  • prefer SILO_RELEASE_SERVICE_HOST for self-hosted release catalogs;
  • replace any reliance on console update with explicit download, verification, and deployment;
  • update process-path-based monitoring to /usr/local/bin/silo-console.

This release does not change the object data layout and requires no bucket or object migration.

Dual Review and Validation Scope

2.0.0 went through two independent review passes before publication. The first pass performed a full code review, fixed the defects described above, restructured 13 intermediate commits into 8 logical ones, and ran Go -race across supported packages, go vet, golangci-lint, govulncheck, frontend type checks, production builds, Prettier, dead-code checks, and the full dependency audit. The second, adversarial pass independently re-ran the core gates and added:

  • all 184 embedded files fetched three ways each (gzip client, identity client, HEAD) with per-file hash comparison against the embedded sources;
  • RFC semantics probes (including combined q-values such as gzip;q=0, *;q=0.5), method restrictions, the OIDC callback, and SPA deep links;
  • full React Router 7 regression: deep links, client-side navigation, bucket-detail tab switching, and browser history back;
  • mobile first-load sidebar behavior, login-page external-request monitoring, and light/dark full-site tours;
  • downloaded release assets verified byte-for-byte against checksums, binary self-reported version confirmed, and a smoke test of the published binary against a live server;
  • zero-diff asset rebuilds confirmed on both macOS and Linux.

The complete pre-rewrite history is preserved in backup refs for rollback.

Known Limitations

  • automatic self-update is disabled; upgrades are explicit;
  • the SSO end-to-end suite requires an external OpenLDAP/Dex/MinIO topology and was not run in that environment this cycle (the OIDC code paths are covered by unit tests and HTTP-level checks);
  • one golang.org/x/crypto advisory has no upstream fix yet and is unreachable from this codebase;
  • SILO does not yet maintain its own video library; videos in the help panel are clearly labeled upstream compatibility material;
  • administrative features depend on the MinIO-compatible Admin API — SILO Console is not a generic browser for arbitrary S3 services;
  • retained Go module paths, environment variables, protocol fields, and the systemd unit name still appear in code, configuration, and process listings.

The complete v2.0.0 change set consists of 8 logical commits:

  • 50797de — feat: establish SILO Console identity and compatibility
  • 23ae6e8 — feat: redesign and harden the SILO Console web app
  • 7a83a77 — build: update Go toolchain and dependencies
  • 1330d25 — fix: eliminate logger shutdown and test mock races
  • 06b3a34 — docs: publish the SILO Console v2.0.0 guide
  • 4b24372 — build: regenerate optimized embedded web assets
  • c38eb64 — ci: package and publish SILO Console v2 releases
  • b952a12 — brand: regenerate the icon set from the official silo.svg emblem

Links:

3 - Silo Pkg 3.11.0 Released

The fork’s first pinned release restores the IAM bucket/object resource boundary that let an object-only grant reach bucket-level writes, fixes a policy condition-key bypass and three LDAP connection defects, and renumbers onto upstream’s 3.11 line after the earlier tags were found to collide with upstream releases of the same numbers.

Release date: 2026-08-04 · Version: v3.11.0 · Commit: d8b1fa7 · Repository: pgsty/silo-pkg

This is the fork’s first pinned release. It restores the IAM bucket/object resource boundary reported as upstream minio/minio#20449: a policy condition-key bypass fix, three LDAP connection defects, a certificate watcher leak, a seeded-RNG defect, and the module’s real minimum Go version.

Warning

Two things to check before upgrading

  1. This release tightens authorization. Twelve bucket-level write actions are no longer reachable through an object-only resource pattern such as arn:aws:s3:::bucket/*. If you write your own bucket-scoped policies, read The IAM bucket/object boundary — the fix is one line of policy for anyone affected, and MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on restores the previous behaviour in full.
  2. The condition-key fix still needs its server half. The policy lookup change and the server changes that reserve internal condition-key names each cover one half of that problem. The companion server work exists in pgsty/minio commit 2f55347f7 but is not yet on public origin/master, and no published Silo server release contains it. Verify that a later server release explicitly includes it.

What This Repository Is

silo-pkg is a maintained fork of minio/pkg, carrying fixes needed by community MinIO forks that the now commercially driven upstream no longer accepts. The repository was renamed from pgsty/minio-pkg on 2026-08-02.

The module path intentionally remains unchanged as github.com/minio/pkg/v3. Existing import "github.com/minio/pkg/v3/..." statements do not change; only the right-hand side of the replace directive does:

replace github.com/minio/pkg/v3 => github.com/pgsty/silo-pkg/v3 v3.11.0

The /v3 suffix is the module’s major version, not a directory name, and must not be omitted. It is also why this release is numbered v3.11.0 rather than v4.0.0: Go requires the major version of a tag to match the major-version suffix declared in go.mod, so a v4.0.0 tag on a .../v3 module is rejected by the toolchain. Publishing a real v4 would mean changing the module path and rewriting roughly 395 import sites across the server, mc and Console — abandoning the drop-in property that is the point of keeping upstream’s path.

The IAM Bucket/Object Boundary

Every bucket-level S3 operation authorizes with an empty object name. The IAM matcher turned that into a resource string and, for the empty-object case, appended a trailing slash:

resource.WriteString(args.BucketName)
if args.ObjectName != "" {
    // "bucket/object"
} else {
    resource.WriteByte('/') // "bucket/"  <-- the defect
}

"bucket/" is matched by the wildcard pattern "bucket/*", because * matches the empty string. A policy granting s3:* on arn:aws:s3:::bucket/* — which reads as “anything, but only on the objects in this bucket” — therefore also authorized bucket-level actions. In a multi-tenant cluster, a tenant holding only that grant could call PutBucketPolicy and install {"Principal":"*"}, making the bucket publicly readable or writable, or grant itself bucket-level control. It could also delete the bucket outright, which is the reproduction in the upstream issue.

The bucket-policy evaluation path used for anonymous access never had this slash and was already reference-correct. Only the IAM path was wrong, in exactly one place.

Why not correct the whole boundary

Removing the slash for every bucket-level request is the obvious fix, and upstream tried it: the change was reverted the same day for breaking policies that relied on the old behaviour. Two properties make the full correction a migration rather than a patch.

It revokes grants real deployments depend on. It does not only revoke the dangerous bucket writes — it also revokes ListBucket, GetBucketLocation and ListBucketMultipartUploads when granted through bucket/*. The evidence is upstream’s own test suite: eleven STS integration tests grant s3:ListBucket on bucket/* and then assert that listing works. If the project that wrote the server writes it that way, production policies do too.

It cuts both directions. The matcher builds the same resource string for Allow and Deny, so removing the slash tightens over-granting Allow statements and simultaneously loosens over-blocking Deny statements. An administrator who locked a bucket with Deny s3:* on bucket/* would silently lose that protection.

How the protected set was chosen

The scope was decided by one question: does reaching this action give the caller something its object-scoped grant does not already provide?

That question is the right one because of how the defect fires. Resource matching runs after action matching, so the bug only bites when the statement already grants the bucket-level action — which in practice means s3:*. The affected principal therefore already holds full read, write and delete over every object in the bucket. The useful question is not how dangerous an action sounds in the abstract, but what reaching it adds to a position that already includes all of the data.

Withheld from object-only grants (twelve actions):

Action Why it qualifies
PutBucketPolicy, DeleteBucketPolicy Hand access to other principals, anonymous included, and can grant the caller bucket-level actions it was never given. Self-escalation and public exposure.
PutBucketObjectLockConfiguration, PutBucketVersioning Defeat protections that exist precisely to stop a holder of write access from destroying data.
PutReplicationConfiguration, PutLifecycleConfiguration Act under server credentials and keep acting after the caller’s access is revoked.
DeleteBucket, ForceDeleteBucket Destroy the bucket entity and its configuration irreversibly. The reproduction in the upstream issue.
PutBucketCors, DeleteBucketCors, PutBucketQOS, PutInventoryConfiguration No server behaviour is attached to these today — no handler at all, or a handler that returns NotImplemented after the authorization check. Withholding them costs nothing and covers them in advance.

Deliberately not withheld, and asserted by a test so that adding one is a deliberate act with a visible cost rather than an edit to a list:

  • PutBucketTagging, PutBucketEncryption, PutBucketNotification. These are bucket-level writes and an earlier draft did withhold them. None gives the caller access it does not already hold — the harm is to the owner’s posture, not to the access boundary — while a tenant handed s3:* on bucket/* and told the bucket is theirs may quite reasonably tag it, set default encryption, or wire up event notifications. Low security gain against a real compatibility cost is the wrong trade for a maintenance release.
  • CreateBucket. It targets a bucket that does not exist yet, so there is nothing to mutate or destroy, and provisioning flows commonly create a tenant’s bucket with that tenant’s own bucket/* credentials.
  • The read/list family (ListBucket, GetBucketLocation, the configuration reads). Breaking these is what got upstream’s own attempt reverted. They wait for a migration-gated release.

Only Allow statements are affected. Deny statements keep the historical resource string, so no bucket lock is ever weakened, and NotResource exclusions keep their full reach.

Monotonicity, and the claim that was wrong twice

All of the above rests on one property: this change may remove permissions and must never add one. That property was asserted twice from reasoning rather than from tests, and was false both times. Recording how is more useful than recording only the final state.

The first attempt let the withheld slash reach the NotResource match as well — and NotResource is an exclusion. An Allow s3:* NotResource bucket/* statement historically did not apply to bucket-level requests on that bucket; matching the exclusion against the bare bucket name made it stop matching, so the Allow it qualified grew, for exactly the writes being protected.

The second attempt fixed that and shipped saying the result was provably monotone. An independent adversarial review of that release produced a counterexample. Withholding the slash does not merely remove a match — it changes which string patterns are matched against, and a pattern can match "mybucket" without ever having matched "mybucket/". A fixed-width wildcard is the clean case:

Allow s3:PutBucketPolicy on arn:aws:s3:::mybucke?

? matches exactly one character. Against the nine-character "mybucket/" it does not match, so this statement never authorized the bucket-level write. Against the new eight-character "mybucket" it does, so the hardening granted something the buggy matcher refused.

The fix is not another special case. On the protected path the matcher now requires both forms to match — the bare bucket name and the historical "bucket/". The result is an intersection with the historical decision, so it is monotone by construction: there is no pattern it can newly satisfy, and no argument left to get wrong. mybucket* still grants (it matched both all along), mybucket/* is still withheld, and mybucke? is refused exactly as it always was.

Two lessons are worth carrying forward. A correctness fix in an authorization path must never make anything newly allowed — and the only way to know is to test both directions, because the reasoning felt airtight in both cases where it wasn’t. And when a security property is load-bearing, build it out of an operation that cannot violate it rather than out of a case analysis believed to be complete.

Evidence

The property is verified rather than argued. A decision corpus of 27,000 authorization outcomes — 15 resource patterns × 3 buckets × 5 object names × 20 actions × 6 statement forms — was generated against both the pre-hardening baseline and this release and compared entry by entry:

Transition Count
false → true (broadening) 0
true → false (narrowing) 144
unchanged 26,856

Every one of the 144 narrowed outcomes falls inside the design intent, with nothing outside it: exactly the twelve protected actions; only the three Allow statement forms, with zero transitions for Deny, NotResource-excluded or deny-NotResource forms; only four object-only resource patterns; and only bucket-level requests, with object-level requests entirely untouched. 12 × 4 × 3 = 144, fully accounted for.

Regression coverage exists at both layers. In this repository, twelve matcher tests pin each direction, including an invariant test that every protected action really is bucket-only — ResetBucketReplicationState, despite its name, is an object action and stays out. In the server, three end-to-end tests drive the real handlers at the client, inline-session-policy and S3-router levels; all three fail against the pre-fix build and pass against this one.

What to change

You are affected only if a stored policy grants one of the twelve actions — or s3:* — on a resource pattern containing /, with no bare bucket ARN for the same bucket. The fix is to add the bare ARN alongside the object pattern:

"Resource": ["arn:aws:s3:::bucket", "arn:aws:s3:::bucket/*"]

That pairing is the conventional form, is what upstream’s own tests use, and worked before this release as well. Built-in canned policies are unaffected — readwrite, readonly, writeonly and diagnostics all use Resource: "*".

MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on, read once at startup, restores the historical matching in full — both the over-granting and the over-blocking. It is a single global switch; per-action scoping is deferred.

Policy Condition-Key Lookup Order

getValuesByKey() previously looked up a policy condition key by its canonical MIME spelling (http.CanonicalHeaderKey) before trying the original name. The map it reads mixes values calculated by the server for the current request (SourceIp, SecureTransport, CurrentTime, username and others, stored under condition-key spellings) with HTTP headers supplied by the request (stored under canonical MIME spellings).

Checking the canonical spelling first allowed a client header to override a value calculated by the server.

For a MinIO server this is a policy bypass. The simplest example is s3:prefix: a Prefix request header could satisfy a home-directory prefix condition while the real ?prefix= query parameter still listed the entire bucket. The same path reached aws:SourceIp, aws:SecureTransport, aws:CurrentTime, aws:EpochTime, aws:username, aws:userid, aws:principaltype, aws:UserAgent, aws:groups, ldap:username, ldap:groups, jwt:groups, s3:versionid, s3:signatureversion, s3:signatureAge, s3:authType and s3:LocationConstraint. Anonymous bucket policies were directly exposed. SigV4 did not prevent the attack because a client can add a header that is not listed in SignedHeaders.

There was a second consequence: when the server stored a value under one spelling and the policy key resolved another, the wrong entry won. s3:object-lock-mode could resolve to the caller’s X-Amz-Object-Lock-Mode header rather than the retention mode the server would actually apply.

The fix reverses the lookup order: match the condition key’s exact name first, then use the canonical spelling only as a fallback for condition keys that genuinely name request headers, such as the s3:x-amz-* family. This ports minio/pkg#226 and adds regression coverage the upstream change did not carry.

At the library’s raw-map layer, if a producer stores one logical field under both the exact condition name and its canonical MIME name, the exact name now wins. This is a library lookup rule, not an S3 wire-protocol rule that says query parameters take precedence. The Silo server first normalizes condition values by their real source. For storage class and upload tagging, where both Header and query forms remain compatible, Header presence wins, including an empty value; query is only the fallback.

LDAP Connection Path

Three defects in connect(). Two were introduced by this fork in b0c08a7 and shipped in v3.6.2 and v3.6.3. Users of either release should upgrade promptly.

StartTLS was skipped when ServerInsecure was enabled. Upstream called StartTLS in an outer block controlled only by ServerStartTLS, so enabling both options created a plaintext connection and then upgraded it. b0c08a7 moved the call into an else branch, making StartTLS unreachable whenever ServerInsecure was true. The connection stayed plaintext and the following bind sent credentials over it. MinIO exposes MINIO_IDENTITY_LDAP_SERVER_INSECURE and MINIO_IDENTITY_LDAP_SERVER_STARTTLS independently and Validate() rejects no combination, so this state was reachable.

This release restores the upstream semantics: the two switches are additive, not mutually exclusive. ServerInsecure disables implicit ldaps://; ServerStartTLS still performs the upgrade. The exposure window is limited to v3.6.2 and v3.6.3.

A Config without a TLS section could panic on the ldaps:// path. After l.TLS.Clone() moved outside the StartTLS branch, ordinary ldaps:// connections also called it. Clone() returns nil for a nil receiver, but the next line assigned ServerName. The MinIO server always supplies TLS settings, but this is a library and mc also consumes it. The code now falls back to an empty tls.Config, matching what DialURL would have built.

StartTLS had no deadline. go-ldap only starts its request timer when requestTimeout > 0, while StartTLS itself has no timeout. A server that completed TCP setup and then stopped responding to the extension request could hold the connect goroutine forever. The timer is now armed before StartTLS.

A failed StartTLS leaked the connection. Inherited from upstream. Dial failures do not return a connection, making StartTLS failure the only connect() path that could return both a connection and an error. Callers only took ownership when the error was nil, leaving a socket behind for every login attempt against a server with a broken upgrade. The failure path now closes the connection and returns nil.

Other Fixes

  • certs: file watchers were never stopped. Manager.AddCertificate() registered two notify.Watch() calls and stopped neither: if the second failed, the first leaked, and both survived until process exit after the manager closed. Certificate.Watch() and watchFile() had the same problem. All four paths now use watchDirSafe(), which returns a stop function invoked on errors and ctx.Done(). This ports the certs/ part of minio/pkg#228. On Windows the function replaces filesystem notification with polling rather than using polling only as a failure fallback, so certificate reload can lag by one symlinkReloadInterval (10 seconds). This fork has no Windows CI; that platform was only cross-compiled.
  • rng: reader subkeys came from a zeroed local variable. init() read 32 bytes of entropy into r.tmp but derived four subkeys from a same-named zeroed local, collapsing four per-block streams into one. Reset() and ResetSize() then replayed the previous stream byte for byte. MinIO creates a new reader for each randreader.New() call and never resets it, so the practical server impact is limited; warp exposed the defect. This ports minio/pkg#230.
  • xtime: Duration implemented UnmarshalJSON but not MarshalJSON. Encoding produced an integer number of nanoseconds while decoding unconditionally stripped the first and last byte and expected a quoted string, so neither direction could round-trip. It now encodes using time.Duration’s string form. This ports minio/pkg#242.

Compatibility Impact

  • Twelve bucket-level write actions are no longer authorized through an object-only resource pattern. See What to change. Object access, ListBucket, CreateBucket, bucket tagging, default encryption and event notification are all unaffected, as are Deny statements and NotResource exclusions.
  • The minimum Go version moves from 1.26.1 down to 1.25.0. A patch number in the go directive is a hard minimum for every consumer, not a record of the toolchain used to build the module. The conventional split is a language version on the go line and a development version on a separate toolchain line. 1.25.0 is what the dependency graph actually requires and what upstream declares. CI builds the complete test suite with Go 1.25 under GOTOOLCHAIN=local, so the minimum is proven rather than aspirational.
  • The JSON wire format of xtime.Duration changes from a nanosecond integer to a duration string such as "2h" or "30m". Persisted numeric values can no longer be read back. No such use was found in MinIO or mc: batch job definitions persist as YAML and the msgp path remains int64.
  • Deployments with both ServerInsecure and ServerStartTLS enabled whose LDAP server does not support StartTLS connected successfully in plaintext on v3.6.2/v3.6.3 and now fail to connect. That is the correct result, but it surfaces during connection rather than configuration validation. Disable ServerStartTLS for such a server.
  • Policy.IsAllowedActions can disagree with a direct decision for the twelve protected actions. It enumerates SupportedActions, which includes the s3:* pattern itself, so the returned set can contain s3:* — and therefore appear to permit a protected action — while the direct evaluation denies it. Nothing in the server calls it, and Console calls it with an empty bucket name, which never reaches the hardened branch. Recorded rather than changed, because altering a public API’s output in a maintenance release is the larger risk.

Divergence from Upstream v3.11.0

The version number follows upstream’s line and makes no claim of identical content. The measured delta, comparing action-string constants across policy/:

Count
Upstream minio/pkg v3.11.0 291
silo-pkg v3.11.0 270

24 actions exist only upstream: six s3:*ObjectAnnotation* actions, five admin: actions (DistJobStatus, Get/SetBucketCompression, two TablesReplication*), and thirteen s3tables: actions covering function CRUD and tagging. These belong to the AIStor vocabulary this fork deliberately does not carry, because the community server does not implement them.

Three actions are named differently on each side. Upstream renamed and split these; this fork retains the earlier names:

silo-pkg v3.11.0 upstream minio/pkg v3.11.0
s3tables:TagResource s3tables:TagTable, s3tables:TagWarehouse
s3tables:UntagResource s3tables:UntagTable, s3tables:UntagWarehouse
s3tables:ListTagsForResource s3tables:ListTagsForTable, s3tables:ListTagsForWarehouse

A policy naming any of these six action strings therefore validates on exactly one of the two. Nothing in the Silo server, mc or Console references them, so there is no impact inside this ecosystem — but a consumer swapping upstream v3.11.0 for this release should know the vocabulary is not interchangeable.

rng has no arm64 assembly. Upstream added rng/xor_arm64.{go,s} after this fork’s divergence point; this release falls back to the pure-Go xor_noasm.go path on arm64. The result is correct and cross-compiles cleanly, but slower than upstream on that architecture. It is a clean candidate for a future sync, being a pure performance change with no vocabulary entanglement.

Companion Server Behavior

  • The condition-key change in this release must be paired with the server changes that reserve internal condition-key names and populate values by semantic source, as noted at the top.
  • s3:signatureAge is exposed only after the SigV4 presigned-request verifier calculates it. A client-supplied x-amz-signature-age Header is ignored on every other request type.
  • s3:prefix, s3:delimiter and s3:max-keys come only from query parameters. Content hash, copy source, metadata directive, SSE and object-lock conditions come only from the corresponding headers. The X-Amz-Content-Sha256 query value consumed while verifying a presigned request does not become a policy condition.
  • s3:x-amz-storage-class retains its compatible query form, as do request tags on PutObject and CreateMultipartUpload. For both fields, Header presence wins and query is used only when the Header is absent.
  • s3:ExistingObjectTag/* comes only from tags loaded from the stored object, so a request’s own X-Amz-Tagging can no longer impersonate existing object state. PutObject, CreateMultipartUpload and PutObjectTagging bind s3:RequestObjectTag/* to the tag input those handlers consume. Other action paths retain the historical X-Amz-Tagging Header fallback for compatibility, so treat request-tag conditions as constraints only where the API actually consumes tags.
  • aws:SourceIp is calculated from forwarding headers. Whether it is enforceable depends on the server’s trusted-proxy configuration; see the server’s own release notes for MINIO_API_TRUSTED_PROXIES.

Verification

Everything below was run against the tagged commit, with the working tree clean and the tag pointing at HEAD:

  • make test — golangci-lint plus go test -race -tags kqueue ./..., all packages passing.
  • go mod tidy -diff clean; gofmt -l empty; go vet ./... clean.
  • Cross-compilation for linux/amd64, linux/arm64, darwin/arm64 and windows/amd64.
  • govulncheck ./... — zero reachable vulnerabilities. One module-level notice remains, GO-2026-5932 in x/crypto/openpgp; that package is unmaintained, has no fixed version, and this repository does not import it.
  • Resolution from an empty module cache through the public proxy, confirming the release is fetchable as published.
  • The 27,000-outcome authorization corpus described above.

Dependencies and Tooling

Dependency updates clear nine reachable findings previously reported by govulncheck: seven x/crypto/ssh issues reached through sftp, GO-2026-6061 in gRPC reached through etcd, and GO-2026-4945 in go-jose reached through oidc.

Five dependencies — minio-go, minio/mux, etcd client/v3, go-oidc and lestrrat-go/jwx — were deliberately not upgraded. MinIO consumes this module through replace, and Minimal Version Selection chooses the highest version in the entire graph, so upgrading them here would also pull the server forward. None has a reported vulnerability requiring that change.

All three workflows previously asked setup-go for a Go version lower than go.mod required and failed on the first Go command; they are now aligned. The linter also fetched an installer from the master branch and reinstalled it on every run. The URL and version are now pinned to v2.11.3, and a matching installed version skips the download.

Changes Deliberately Not Taken from Upstream

  • AIStor policy vocabulary (Memory/cortex, Tables/Iceberg, KMS, compression and annotations) and the typed action-constant refactor, none of which the community server implements. This is the source of the action-vocabulary delta.
  • securityAuditAdmin, which grants admin:ExportIAM and therefore exposes every secret key despite what the name suggests.
  • rng AVX2/NEON assembly. Revisiting the arm64 half is noted above as a future sync candidate.
  • net.BandwidthBytesPerSec (declared but never read upstream), replicationAdmin and DistJobStatusAction.
  • Two changes initially taken and removed after review: the consolereadonly built-in policy and GetAllGlobalCertificates. Neither has a consumer. Once operators bind a built-in policy name to users, withdrawing it is particularly unsafe: policy mappings persist by name, and an unresolved name merges into an empty policy that denies everything. Its inherited admin:CreateUser Deny also cannot be combined with iamAdmin. The certificate helper inventoried a cache the community server never populates.
  • Upstream’s golangci-lint tool directive, which would add roughly 200 linter dependencies to every downstream consumer’s module graph.

Deliberately Deferred

The general problem in minio/minio#20449 — that bucket/* still reaches ListBucket, GetBucketLocation, the configuration reads, CreateBucket and the three tenant-plausible writes — is not closed here. Closing it means revoking grants real deployments depend on, so it belongs to a release that carries a migration path.

What that release owes operators is more than a longer action list, because no one can enumerate every deployment’s stored policies — which puts a hard ceiling on any approach that picks the protected set by guessing. Three things raise it:

  • A startup policy audit that walks stored policies and names each one whose meaning changes, in both the grant and the deny direction. It is read-only and can ship before the enforcement change rather than with it, turning an upgrade surprise into a pre-upgrade checklist.
  • A denial that explains itself. When a request is refused because only an object-scoped grant matched, say so and name the compatibility switch. A break an operator can diagnose in thirty seconds costs an order of magnitude less than a silent one.
  • A switch with a scope. MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH is all-or-nothing today, so an operator who needs one action back must reopen the self-escalation path along with it.
  • d8b1fa7: fix(policy): settle the bucket-write hardening’s scope and monotonicity
  • 1f97549: fix(policy): extend the bucket-write hardening to every bucket-only write
  • 3c24ad1: fix(policy): withhold object-only grants from sensitive bucket writes
  • da6a22a: docs: say what this fork is and how to depend on it
  • 4055b2f: fix(xtime): marshal Duration as a duration string
  • 13c26cd: fix(rng): initialize the reader subkeys from the seeded entropy
  • 88b37ac: fix(certs): stop file watchers on every exit path
  • 74dd36e: fix(ldap): keep StartTLS when ServerInsecure is also set
  • 424c3d0: fix(ldap): close the connection when StartTLS fails
  • 045d10f: fix(ldap): guard a nil TLS config and arm the StartTLS deadline
  • 5c4bf50: fix(policy): prefer the exact key name over the canonical header form
  • 802539f: chore(deps): refresh the dependency set and declare the real minimum Go
  • e4ec64a: ci: build on the Go version go.mod requires, and prove the declared minimum
  • 747d8b8: build: pin the golangci-lint installer and skip a matching install

4 - mcli 20260806 Released

The client adopts the Silo identity end to end: every MinIO SUBNET path is disabled at build time, the embedded vendor diagnostics-encryption key is gone, and contributions move to a no-CLA, DCO-required policy.

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

Two days after mcli 20260804, this release completes the client’s transition to the Silo identity. It is deliberately a pure rebranding and lockdown release: --version and --help now present the Silo client, every remaining path to MinIO’s SUBNET service is disabled at build time, the embedded vendor encryption key is removed from the diagnostics tooling, and the contribution policy moves to no-CLA with a mandatory DCO sign-off. There are no dependency changes and no protocol changes in this cycle — go.mod is byte-for-byte identical to 20260804 — so the regression surface is confined to text, command gating, and CI.

Warning

Behavior changes

Every path that previously reached MinIO SUBNET is now disabled at build time and cannot be re-enabled at runtime:

  • mcli license register, mcli support upload, mcli support proxy set, mcli support callhome enable, and the online-renewal form of mcli license update ALIAS print a stable notice — “MinIO SUBNET services (registration, licensing, uploads) are disabled in this Silo build of mc; diagnostics remain available locally.” — and always exit 1. Drop these calls from scripts. The file-based mcli license update ALIAS license.key still works, with the license parsed offline against the bundled public key.
  • mcli support diag / perf / profile / inspect always operate in local (airgap) mode: reports, profiles, and inspect archives are written to local files and nothing is uploaded anywhere. The --airgap flag is still accepted for compatibility and is effectively always on. SUBNET registration is no longer a prerequisite for any of them.
  • mcli support callhome disable|status, mcli support proxy show|remove, mcli license info, and mcli license unregister keep working — they only read or clear local and server-side configuration.
  • Fresh configurations no longer seed the play alias pointing at MinIO’s public demo cluster; the defaults are now local, s3, and gcs. Existing configuration files are never modified, and legacy-config migration still recognizes the historical entries.
  • mcli --version gains an identity line (“Silo object storage client, based on MinIO technology”) and a second copyright line. The first line’s machine-readable format is unchanged, so scripts parsing it are unaffected.

Major Changes

  • Silo identity across the CLI: the client introduces itself as “Silo client for object storage and filesystems”. Roughly 220 help texts were reworked: usage lines that refer to the managed server now say “Silo/MinIO server”, example aliases moved from myminio/play to mysilo, example LDAP DNs moved to dc=example,dc=com, and example tier names to SILOTIER-*. Factual references stay factual: the minio tier type, protocol headers, and third-party interop mentions are untouched.
  • SUBNET disabled at build time: connectivity is compiled out behind a single guard, and the one HTTP choke point that every SUBNET request funnels through refuses with the stable error above. Command entry points gate early, diagnostics force local mode, and the AGPL license notice shown by mcli license info no longer carries a commercial-subscription pitch. A dedicated regression suite (cmd/subnet-disabled_test.go) pins all of this, so an upstream merge cannot silently reconnect anything.
  • Governance — no CLA, DCO required: contributions are accepted inbound=outbound under AGPL-3.0-or-later; contributors keep their copyright, and the maintainers collect no rights beyond the project license. Every commit must carry a Signed-off-by trailer, enforced by a new CI workflow that matches the trailer against the commit author’s email and exempts only GitHub-issued bot addresses. CONTRIBUTING.md, the PR template, and both READMEs document the policy, and the code-of-conduct contact now points at the fork’s maintainer.
  • Dual copyright attribution: runtime output and help now credit both lineages — Copyright (c) 2015-2025 MinIO, Inc. and Copyright (c) 2025-2026 PGSTY — with source builds computing the end year dynamically. NOTICE states the fork relationship, and the non-affiliation with MinIO, Inc., explicitly.
  • Release line renamed to main: workflow branch filters, documentation, and contributor instructions now target main; the legacy master references are gone.

Hardening

  • Vendor encryption key removed: mcli support inspect used to fall back to encrypting its output with an embedded MinIO RSA public key whenever no key was supplied — producing archives only the vendor could decrypt. The embedded key is gone: inspect now relies on the server-generated per-request key that is printed to the caller (or an operator-supplied key), and any encrypted-upload path with no configured recipient fails closed instead of silently borrowing a third-party key. Diagnostic output an operator produces is now always decryptable by that operator.
  • Brand-policy gate: buildscripts/check-branding.sh runs in make verifiers and in CI. It fails the build if MinIO-operated endpoints, commercial upsell URLs, the upstream product identity, or any embedded MII… public key reappear in the command tree — while explicitly allowlisting the preserved compatibility identifiers (environment variables, protocol headers, module path, legacy-migration defaults, and original copyright headers).

Engineering and Delivery

  • CI moved to the Node 24 Actions line: actions/checkout v7, actions/setup-go v7, goreleaser-action v7, and the Docker action family — all still pinned to commit SHAs, with dependabot keeping the pins current.
  • Functional tests run against controlled servers only: the suite defaults to a local server (localhost:9000) instead of MinIO’s public demo cluster, and CI downloads the pinned SILO server release RELEASE.2026-08-04T00-00-00Z from pgsty/silo, verified by SHA-256, before running the suite.
  • Zero dependency changes: no module updates this cycle; the 20260804 security baseline (Go 1.26.5, zero known reachable vulnerabilities) carries over unchanged.
  • Audited before tagging: the release was gated by an independent adversarial review — a full read of the 245-file diff, brand/compatibility grep sweeps, call-graph verification that no command or flag combination can reach subnet.min.io / play.min.io / dl.min.io, and smoke tests confirming every disabled path returns its stable error with exit status 1.

Compatibility

Everything scripts and integrations depend on is deliberately unchanged: the mc command name and the mcli package/binary name; the ~/.mc / ~/.mcli configuration directories (derived from the invoked name); the github.com/minio/mc module path and all import paths; MC_* environment variables; protocol headers (x-minio-*) and the minio-go SDK user-agent prefix; the minio tier type; the .part.minio transfer suffix; the minio-job Prometheus scrape job name; and the package formats, asset naming, and YYYYMMDDHHMMSS.0.0 version scheme. The client remains fully compatible with MinIO servers and other S3-compatible endpoints.

Note

Known issues

The mcli watch regression flagged in the 20260804 notes is resolved on the server side: the fix shipped in SILO 20260804, and this client’s CI now runs the functional suite — including watch — against exactly that release. Pair mcli with SILO server 20260804 or newer to receive bucket events; older published servers remain affected.

Unfixed upstream defects continue to apply, most seriously minio/mc#5139: mirror --remove --watch can delete a live object from the target when a non-current version of it is removed from the source. Exercise caution combining --remove --watch on versioned buckets.

  • 8a883ca: ci: move the branch filters to main and fetch the server from pgsty/silo
  • 8c304dd: ci: move the pinned actions onto the Node 24 runtime
  • 02b1c11: docs: name the release line main, not master
  • 810bbd2: ci: pin the functional-test server to a release whose watch API works
  • d145647: fix: disable SUBNET connectivity and licensing upsell paths
  • 5061c4f: rebrand: adopt Silo identity in CLI help and examples
  • c7f7706: docs: align governance files and package metadata with the fork
  • 65c71b2: test: default functional tests to a local server and add brand gate
  • c62a64d: fix: credit both MinIO and PGSTY in copyright notices
  • d205f88: docs: adopt no-CLA plus DCO contribution policy
  • 95326ce: docs: add related-projects table and polish contribution wording
  • d2c0db7: fix: remove the vendor encryption key and close the proxy-set path
  • 0c6704d: fix: repair a link and help text damaged by the brand sweep

5 - Silo 20260806 Released

The first release published under the Silo name: the completed rebrand with compatibility preserved, native health checking, a distroless image pilot, bundled mcli 20260806, full license compliance, and a provenance-gated release pipeline.

Version: RELEASE.2026-08-06T00-00-00Z · Commit: 3be10fcc1a44f6620ded0bd303461f9d688cca23

SILO 20260806 is the first release published under the Silo name. The previous release, 20260804, was the last one delivered as pgsty/minio; this release completes the cutover to github.com/pgsty/silo and renames every delivery surface — binary, packages, container images, systemd unit, Helm chart — while deliberately preserving every wire and configuration surface a MinIO deployment depends on. On top of the rename it adds native health checking (silo healthcheck), a single-binary distroless container image pilot, complete license-compliance materials in every artifact, and a release pipeline gated on compatibility snapshots and build provenance.

The release covers 28 commits after RELEASE.2026-08-04T00-00-00Z, changing 396 files with 27,188 insertions and 19,561 deletions. It passed a six-phase pre-release acceptance, including a real four-node TLS cluster migration from MinIO to Silo — with byte-verified data integrity, maintenance-gated rolling restarts, fault injection, and a full rollback rehearsal.

Highlights

  • The rebrand is complete, and compatibility is the contract. Repository, binary (/usr/bin/silo), packages (silo rpm/deb/apk), images (docker.io/pgsty/silo), and service (silo.service) are renamed; the S3 and admin APIs, /minio/* routes, MINIO_* environment variables, x-minio-* headers, on-disk .minio.sys format, and Go module paths are all preserved and frozen by a CI compatibility guard.
  • Native health checking: silo healthcheck [live|ready|cluster|cluster-read] probes the server’s own health API with correct exit codes, decoded quorum diagnostics, TLS auto-detection, and a --maintenance pre-drain gate — no shell, curl, or mc required in the container.
  • Distroless image pilot: pgsty/silo:<RELEASE>-distroless ships exactly one program — the silo binary — on gcr.io/distroless/static, with an exec-form HEALTHCHECK baked in and /data created writable in the image layer.
  • The classic image does not change behavior: same entrypoint, same bundled tools, mc ready local keeps working, and no HEALTHCHECK was added to it. It now bundles mcli 20260806.
  • Compliance completed: LICENSE and NOTICE ship in every package and image, CREDITS is regenerated from the actually-linked module set (291 modules) and guarded in CI, and the project adopts a no-CLA, DCO-based contribution policy.
  • Components refreshed: embedded SILO Console 2.1.1, silo-pkg 3.11.0, mcli 20260806, Go 1.26.5.
  • Provenance-gated releases: container images are built only from published, checksum- and attestation-verified release archives; image SBOMs and provenance attestations now cover the distroless variant too.

The rename

What changed, and what deliberately did not:

Renamed (delivery surface) Preserved (compatibility surface)
Repository: github.com/pgsty/silo (main branch) S3 API, admin API, and request signing behavior
Binary: /usr/bin/silo /minio/* routes, including /minio/health/* and metrics
Packages: silo-*.rpm, silo_*.deb, silo_*.apk MINIO_* environment variables and x-minio-* headers
Images: docker.io/pgsty/silo (+ -distroless) On-disk format (.minio.sys), erasure coding, versioning
Unit: silo.service (conflicts with, and supersedes, minio.service) Go module and import paths (github.com/minio/...)
Default config dir: ~/.silo (falls back to an existing ~/.minio) mc compatibility alias for the bundled mcli

The server presents its own identity — silo --version reports the AGPL-3.0 license, MinIO’s 2015-2025 copyright, PGSTY’s modification copyright, and the “based on MinIO technology” attribution — and every inherited connection to MinIO-operated services (the update feed and its signing key, SUBNET, telemetry) is severed rather than redirected. The container entrypoint translates the legacy minio argv token, so docker run pgsty/silo minio server /data keeps working.

A snapshot-based rebrand guard runs in CI: it fails on any drift, in either direction, across 334 route literals, 437 environment tokens, 84 headers, and 9,014 exported symbols.

Native health checking

The server binary can now probe its own health endpoints, which makes container health checks possible without any second binary — and is what the distroless image relies on:

silo healthcheck [FLAGS] [live|ready|cluster|cluster-read]
  • The check vocabulary maps 1:1 onto /minio/health/<path>; live (the default) answers “is this process serving,” ready adds KMS/etcd reachability when configured, and the cluster pair evaluates write/read quorum across every erasure set.
  • Exit codes are 0 (healthy) and 1 (anything else) — never the Docker-reserved 2. One diagnostic line decodes the server’s x-minio-server-status and quorum headers for docker inspect; --json emits a machine-readable verdict.
  • The probe target is derived the way the server derives its own listen address: --address / MINIO_ADDRESS, with HTTPS auto-detected from public.crt + private.key in the certs directory, or overridden wholesale with --url / MINIO_HEALTHCHECK_URL. The environment form exists because a probe process cannot see the server’s command line — if the server’s address or TLS comes from CLI arguments, one environment variable redirects the baked-in probe.
  • silo healthcheck --maintenance cluster answers the pre-drain question: exit 0 means the node can be taken down without losing HA; HTTP 412 (exit 1) means it cannot.
  • Certificate verification is skipped, matching the kubelet’s documented behavior for HTTPS probes, and the transport ignores HTTP_PROXY so loopback probes never route through a proxy.

Kubernetes needs none of this — kubelet httpGet probes hit /minio/health/live and /minio/health/ready from outside the container — and the cluster checks should stay out of per-container probes: they reflect cluster-wide quorum, not one process. The full design rationale, including verified endpoint semantics, is recorded in the health-check design note.

Distroless image pilot

Alongside the classic image, this release publishes a distroless variant: pgsty/silo:RELEASE.2026-08-06T00-00-00Z-distroless, plus a rolling distroless tag.

  • Base is gcr.io/distroless/static-debian12: CA certificates, tzdata, /tmp, and an /etc/passwd with a nonroot (65532) entry — no shell, no package manager, no libc. On top of it, exactly one program: /usr/bin/silo (plus the license set under /licenses/). The image is 128 MB versus the classic 199 MB.
  • The binary is the ENTRYPOINT; an exec-form HEALTHCHECK running silo healthcheck ready is baked in (interval 30s, timeout 10s, start-period 2m, retries 3), so Compose users get working depends_on: condition: service_healthy with zero configuration.
  • /data is created in the image layer, world-writable — there is no entrypoint left to repair volume ownership at runtime, and this is what makes every privilege mode work, --user included. This fixes, for the distroless variant, the non-root failure documented in #55.
  • Not supported in this variant: the deprecated MINIO_USERNAME/MINIO_GROUPNAME privilege-drop path (use --user or Kubernetes runAsUser), docker exec <c> sh debugging (use ephemeral-container tooling), and in-image mc (use the released mcli or the client image).
  • TLS: mount certificates at /tmp/.silo/certs (the container’s default certs directory) and both the server and the baked-in probe derive HTTPS from the same location; for CLI-configured servers, set MINIO_HEALTHCHECK_URL.

The classic image remains the default and is unchanged. If the pilot proves out, the distroless variant becomes the recommended image later; the decision record lives in the design note above.

Container images

The classic image was diffed field by field against pgsty/minio:RELEASE.2026-08-04T00-00-00Z: entrypoint, exposed ports, volumes, working directory, user, and (absent) health-check configuration are identical. Exactly three differences exist, all deliberate: Cmd is ["silo"] instead of ["minio"], the upstream update-verification key variable MINIO_UPDATE_MINISIGN_PUBKEY is removed (updates through upstream channels are permanently disabled), and HOME=/tmp is declared to match the entrypoint’s writable-home guarantee.

The bundled client is upgraded to mcli RELEASE.2026-08-06T00-00-00Z (with the mc alias preserved), pinned by per-architecture SHA-256 digests and verified against the published checksums at build time. Interoperability of the released mcli 20260806 against this server — multipart, versioning, presigned URLs, metadata/tags, user and policy administration — was verified as part of release acceptance.

Helm chart

The chart ships as silo 7.0.1, preserving rendered resource identity with the legacy chart across a simulated upgrade (verified by the migration guard over 7 rendered resources). Its default image tag now points at this release — docker.io/pgsty/silo is a fresh repository, so the inherited default could never have pulled. The chart still ships no liveness/readiness/startup probes; adding them is planned, and documented, in the design note’s follow-up phase.

Packaging and migration

RPM, DEB, and APK packages install exactly six files: /usr/bin/silo, silo.service, a sysusers definition (creating the silo system user), /etc/default/silo (marked config/noreplace), LICENSE, and NOTICE. RPMs are GPG-signed with the PGSTY maintainer key (9592A7BC 7A682E73 33376E09 E7935D8D B9BD8B20). RPM and DEB now carry a unified, PGDG-style 1PGSTY release segment — silo-<version>-1PGSTY.<arch>.rpm and silo_<version>-1PGSTY_<arch>.deb — replacing the inherited bare -1 on RPM and the missing revision on DEB; APK names stay bare because Alpine pkgrel admits only -r<integer>.

silo.service is designed for takeover: Type=notify (readiness is signaled by the server itself), Conflicts=minio.service + After=minio.service (starting Silo stops a running MinIO unit), and two environment files — /etc/default/minio is read first and /etc/default/silo overrides it — so an existing MinIO configuration is inherited without editing. For existing deployments whose data is owned by the minio user, the documented drop-in keeps ownership untouched:

# /etc/systemd/system/silo.service.d/10-legacy-user.conf
[Service]
User=minio
Group=minio
Warning

Distributed migrations must switch all nodes together.

Cluster bootstrap verifies that every node runs the same binary (by checksum). A mixed cluster — some nodes on Silo, some still on MinIO — does not form: the new node stays in activating, logging Expected Silo binary checksum ... seen: ... and Waiting for at least 1 remote servers with valid configuration, indefinitely. Stop MinIO on all nodes, then start Silo on all nodes (near-simultaneously). Once every node runs Silo, rolling restarts work normally — gate each one with silo healthcheck --maintenance cluster.

Migration troubleshooting, from the acceptance run: if Silo starts as the packaged silo user against a deployment whose TLS certificates live under the minio user’s home, it fails with HTTPS specified in endpoints, but no TLS certificate is found and restart-loops until the systemd start limit — the legacy-user drop-in above is the fix. Keep the MinIO package and unit installed (disabled) during the migration window: the rollback path — stop Silo, start MinIO — was rehearsed and reads all data written during the Silo window, because the migration touches neither data ownership nor format.

Components and dependencies

  • SILO Console 2.1.1 — the embedded console, selected from pgsty/silo-console while preserving the github.com/minio/console import path.
  • silo-pkg 3.11.0 — retains the policy/LDAP/certificate fixes including the LDAP-over-TLS repair tracked in #15.
  • mcli 20260806 — bundled in the image and released separately; see its release notes.
  • Go 1.26.5 — toolchain unchanged from 20260804.

Build, CI, and release pipeline

  • Compatibility as a CI gate: the rebrand guard snapshots routes, environment tokens, headers, metrics, storage/policy identifiers, and exported symbols, and fails on any unreviewed drift; companion scripts assert the delivery surface (binary path, unit contents, image layout) and that no live upstream endpoint remains in runtime code.
  • Release-image gate: every release-pipeline run builds both container images and asserts, among others: the distroless HEALTHCHECK survives into the image config (it is a Docker extension outside the OCI spec), /data ships world-writable, no shell and no /usr/bin/minio exist, Docker’s health state turns healthy from the baked probe alone, and SIGTERM still stops the server gracefully as root and as --user 1001:1001.
  • Provenance chain: images are built from the published release archives after checksum verification and gh attestation verify against the exact tag; per-architecture SBOMs and provenance attestations are pushed for the classic and distroless images; the distroless health-check gate runs before the multi-arch manifests are promoted.
  • Workflow runtime moved to Node 24 across CI actions.

Compatibility and upgrade notes

  1. Package upgrades are a takeover, not an in-place update. Install silo, keep /etc/default/minio as is (it is inherited), enable silo.service; starting it stops minio.service via the conflict relation. Data is untouched.
  2. Keep data ownership stable with the legacy-user drop-in above; do not chown storage or move certificates during migration.
  3. Distributed clusters: full-stop switchover only. See the warning above — mixed Silo/MinIO nodes do not form a cluster.
  4. Container users: the image is now docker.io/pgsty/silo; docker.io/pgsty/minio stays frozen at 20260804 as an archive. The classic image’s behavior is unchanged — including mc ready local health checks — and the distroless variant is strictly opt-in.
  5. Distroless differences are deliberate: no shell, no in-image mc, no MINIO_USERNAME path; health is native; servers configured via CLI arguments need MINIO_HEALTHCHECK_URL for the baked-in probe.
  6. Helm users: chart 7.0.1’s defaults now pull this release; override image.tag explicitly if you pin versions.
  7. Known and unchanged: the classic image still does not create /data in the layer, so fully non-root docker run against a Docker-managed volume fails as before (#55, fixed in the distroless variant); the inherited Postgres/MySQL legacy notification-migration limitation from the 20260804 notes still applies (#53).
  8. Pair with mcli 20260806 for the client side; older clients continue to work over the unchanged wire protocol.

Verification

This release was verified in stages, each with recorded evidence:

  • unit and end-to-end matrices for the health-check command: target derivation and precedence (flag/env/derived), real-TLS auto-detection, exit-code contract, JSON schema, timeout bounds, usage errors;
  • cluster-semantics verification on a four-node cluster: with 2 of 4 nodes stopped, cluster reports 503 with write-quorum=5 while cluster-read and live stay 200 — the write/read quorum split observed live, matching the erasure math;
  • image acceptance: the classic image diffed field-by-field against the 20260804 baseline; the distroless image asserted down to file inventory, exact health-check configuration, and root/non-root/TLS/env-override runtime scenarios;
  • an adversarial model-based code review of the new code, with every confirmed finding fixed and re-verified;
  • a six-phase pre-release acceptance concluding in a real migration: a Pigsty-deployed four-node TLS MinIO 20260804 cluster (16 drives, EC:4) was migrated to Silo via the packaged takeover path — reference data (multipart, versioned, tagged objects) read back byte-identical, four maintenance-gated rolling restarts, kill -9 fault injection with the load balancer serving 23/24 continuous IO rounds (the only failure in the kill second), Prometheus metrics continuity, and a full rollback to MinIO and back, proving the migration reversible.

Validation boundaries

Not proven by this release and not to be inferred: external LDAP/OIDC/KMS/etcd services (the only case where ready diverges from live was not exercised against a live KMS); amd64 packages were cross-built and payload-checked but not installed on a physical x86-64 host; the renamed Docker publish workflow (including the new SBOM/attestation lanes) has its first production run at this release’s publication; Windows and Intel macOS were not tested.

Artifacts

  • GitHub release RELEASE.2026-08-06T00-00-00Z at pgsty/silo, with checksummed platform archives, provenance attestations, and RPM/DEB/APK packages (GPG-signed RPMs);
  • docker.io/pgsty/silo:RELEASE.2026-08-06T00-00-00Z and latest; docker.io/pgsty/silo:RELEASE.2026-08-06T00-00-00Z-distroless and distroless — published on demand from the finished release;
  • companion releases: mcli 20260806, silo-pkg 3.11.0, embedded SILO Console 2.1.1;
  • design record: Native Health Checks and the Distroless Image.

Selected changes

  • 15def34dc, 77bdc4c0c: drop upstream delivery residue; present Silo identity and close inherited upstream services
  • 15ab10833: rename the delivery artifacts to silo and complete the package payload
  • 30749911b: ship the silo binary in the image and translate the legacy argv command
  • e071bb77e: replace the minio chart with a silo chart that preserves identity
  • bd8df5166: gate the rebrand on compatibility, packaging, and provenance evidence
  • 6613c2a3c: pin the external test fixtures and run the suites against the silo binary
  • fd2ca1c6d, c46b16ec6, c47733abc, f1c77d5a2: cut over to pgsty/silo and main; document the archived branch
  • 6740e6978: move the workflow actions onto the Node 24 runtime
  • b57275be3: adopt the no-CLA plus DCO policy and fix copyright terms
  • 62717d7bf, a6d6d9b02: update the embedded Console to 2.1.0, then 2.1.1
  • 6bd9cf77e: regenerate CREDITS from the linked module set and guard it in CI
  • 219670d31: ship LICENSE and NOTICE in every package and image
  • 2ff594f4b: add the native silo healthcheck subcommand
  • 4c34d2309: add the distroless image variant as a pilot
  • b6d47b739, 9462cce16: harden both per adversarial review; lint cleanup
  • 16b78eb4e: bundle mcli 20260806 and point the Helm defaults at this release
  • 062a91bee: pin the CREDITS module closure to the shipped linux target
  • 467931455: unify the rpm and deb release segment as 1PGSTY
  • b14ea22aa: match checksum manifest entries exactly in the image publish lane
  • 3be10fcc1: add a manual finalize lane refreshing SBOMs and checksums for signed Draft packages

Acknowledgments

Four contributors have code merged into this fork, and the Git history carries their authorship: @ZouhairCharef patched CVE-2026-34986 in go-jose (#18), @mfredenhagen patched CVE-2026-39883 in OpenTelemetry (#19), @pinginfo implemented Flush on trackingResponseWriter to repair bucket notification streaming (#34), and @waterkip repointed the documentation links to the Silo portal (#41).

A first release under a new name is also the right moment to thank everyone who has filed issues against this fork — bug reports, compatibility findings, and proposals alike, resolved and still open:

@mosesdd (#1), @Xavier-777 (#2, #17), @jiadzh (#3), @TLINDEN (#4), @AntonOfTheWoods (#5), @zylpsrs (#6), @nsanitate (#7), @makinikm (#9), @magicxor (#10), @spaceg00se-r (#11, #14), @heroes1412 (#13), @vampywiz17 (#15), @davinkevin (#20), @chalukyaj (#30), @cbornet (#31, #32), @jvasile (#33), @Kesavaambati (#35), @redfoxfox (#38), @kuldeep-link11 (#39, #40), @meesudzu (#42), @pmezhuev (#43), and @kh0mka (#51).

Several of this release’s headline items trace directly back to those reports: the bundled-client guarantee to #4 and #9, the LDAP-over-TLS repair to #15, the completed package payload to #33, GPG-signed RPMs to #43, the migration guide to #42, and the distroless /data fix to #55.

Pull requests still in flight deserve a mention too. @davinkevin’s distroless image PR (#21) anticipated this release’s pilot months in advance — the shipped variant supersedes that PR with the native health check built in, but the direction was proposed there first. Conformance PRs from @magicxor (#12) and @ycjlin (#37) are queued for review immediately after this release.

Everyone who has contributed to this fork is recorded in CONTRIBUTORS.md, which is now the project’s attribution record — GitHub generates no contributor graph for forks.

6 - Silo 20260804 Released

Internode storage containment, S3/IAM policy hardening, multipart correctness, streaming reliability, notification fixes, Go 1.26.5, and a rebuilt, signed release pipeline.

Version: RELEASE.2026-08-04T00-00-00Z · Commit: d88f46ccee345a9c2fabe2d221d9a9e56bc11aec

SILO 20260804 is a security, correctness, and release-engineering update to the pgsty/minio community fork. It completes the internode storage-containment work begun with CVE-2026-42600, prevents request-controlled values from impersonating server-calculated S3/IAM policy conditions, restores streaming flush behavior, fixes several multipart and versioning edge cases, hardens notification configuration migration, moves the build baseline to Go 1.26.5, and connects the server to the SILO-maintained Console, shared package, and mcli releases. The release pipeline was rebuilt to produce reproducible binaries and GPG-signed packages.

The release covers 50 commits after the pre-2026-06-18 baseline, changing 155 files with 9,241 insertions and 981 deletions. Every change was reviewed against the tagged commit and verified on macOS ARM64 and Linux AMD64, with GitHub CI green on the released HEAD.

Highlights

  • Internode containment completed: validates storage-REST message bodies, storage Grid frames, and peer-S3 Grid requests at the storage boundary, closing the remaining path, volume, erasure-metadata, panic, and unbounded-allocation defects left after removing ReadMultiple.
  • S3/IAM decisions now use effective values: client input can no longer shadow internal condition values; request tags and existing-object tags are separated; s3:signatureAge is confined to verified presigned requests; and s3:versionid follows the version the server actually acts on.
  • Bucket and object resources are separated: twelve sensitive bucket-level writes are no longer authorized through an object-only bucket/* resource pattern. A documented compatibility switch is available for migration.
  • Multipart compatibility and correctness improved: full-object checksum completion works without per-part checksums when the protocol permits it, zero-length multipart checksums are preserved, and duplicate part numbers are rejected instead of assembling duplicated data.
  • Streaming reliability restored: trackingResponseWriter now implements Flush correctly and records implicit HTTP 200 responses, repairing mcli watch, bucket-notification listeners, and S3 Select keep-alives affected by the inherited regression documented in the 20260618 release.
  • Notification configuration hardened: NATS and AMQP keys used by parsers and legacy migration are registered and round-trip correctly; libpq connection parameters are quoted safely; invalid-key errors no longer echo secret values.
  • Reproducible, signed release pipeline: binaries no longer embed the build machine’s paths, packages install under the canonical systemd path, and RPMs are GPG-signed. The container entrypoint now shuts down gracefully on every privilege path.
  • Release baseline refreshed: Go 1.26.5, klauspost/compress 1.18.7, Apache Thrift 0.24.0, SILO Console 2.0.0, silo-pkg 3.11.0, and mcli 20260804.

Security Hardening

Internode storage and Grid containment — SN-2026-002

Removing the obsolete ReadMultiple endpoint in 20260618 closed one reachable path but did not close the underlying defect class. Storage-REST request bodies and Grid RPC frames do not pass through the HTTP query-validation middleware, and peer-S3 RPCs can bypass the storage-REST wrapper entirely.

This release moves containment to the storage boundary and validates every caller-controlled path, volume, erasure parameter, part size, shard length, and allocation length before use. The fixes include:

  • reject traversal on both path and volume axes, including Windows volume-root aliases;
  • cover peer-S3 bucket RPCs that reach drives without the storage-REST wrapper;
  • reject zero or unusable data/parity/block-size combinations before shard arithmetic;
  • reject negative part sizes and truncated shards instead of reporting them healthy;
  • cap storage-REST ReadFile allocations at 5 GiB;
  • bound other allocations derived from internode declarations;
  • contain panics in deadline-bounded storage work without blocking the caller;
  • preserve ReadParts errors across keep-alive responses and avoid the empty-part trace panic.

These routes require cluster-root or internode credentials and are registered only in distributed-erasure deployments. Single-node S3 behavior is unchanged. See Internode Path Containment Audit for the protocol-surface analysis.

Effective policy-condition values — SN-2026-003

The policy condition map historically mixed values calculated by the server with raw request entries. A client-controlled spelling could therefore shadow or synthesize an internal condition value. SILO 20260804 pairs silo-pkg 3.11.0’s exact-key lookup rule with server-side source normalization:

  • internal condition names cannot be supplied as arbitrary client values;
  • s3:prefix, s3:delimiter, and s3:max-keys come from their effective query inputs;
  • header-backed x-amz-* conditions do not accept unrelated query substitutes;
  • when storage class or upload tagging supports both forms, an explicitly present Header wins, including an empty Header;
  • s3:ExistingObjectTag/* comes only from stored object metadata;
  • s3:RequestObjectTag/* is bound to the tag input consumed by the relevant operation;
  • s3:signatureAge is exposed only after verified SigV4 presigned authentication calculates it;
  • s3:versionid is absent when no version is named and is rebound per DeleteObjects entry to the effective resolved version.

The version-ID behavior closes the fail-open trap that a superficial “omit empty values” fix would have created for Multi-Delete. See Absent Is Not Empty.

Bucket/object resource boundary — SN-2026-004

The IAM matcher used to append a slash to a bucket-level request, allowing an object-only resource such as arn:aws:s3:::bucket/* to authorize selected bucket-level operations. This release withholds twelve sensitive writes from that pattern on Allow statements:

PutBucketPolicy, DeleteBucketPolicy, PutBucketObjectLockConfiguration, PutBucketVersioning, PutReplicationConfiguration, PutBucketLifecycle, DeleteBucket, ForceDeleteBucket, PutBucketCors, DeleteBucketCors, PutBucketQOS, and PutInventoryConfiguration.

Deny and NotResource behavior is unchanged. Read/list operations, CreateBucket, bucket tagging, default encryption, and notification configuration remain compatible. Built-in policies use Resource: "*" and are not affected.

Warning

Policy migration required for custom bucket grants

If a custom policy grants one of the twelve actions — often through s3:* — using only arn:aws:s3:::bucket/*, add the bare bucket ARN:

"Resource": ["arn:aws:s3:::bucket", "arn:aws:s3:::bucket/*"]

MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on restores the historical matcher while policies are migrated. It also restores the historical over-grant, so use it only as a temporary rollback control.

Trusted client-address boundary

MINIO_API_TRUSTED_PROXIES provides an enforceable, opt-in boundary for aws:SourceIp, audit remotehost, event notification Host, and the client address shown by mcli admin trace:

  • set it to an address/CIDR list to trust forwarding headers only from those peers and walk forwarding chains from right to left;
  • set it to none to ignore all forwarding headers;
  • leave it unset to preserve historical behavior exactly.

The old _MINIO_API_XFF_HEADER=off switch still suppresses only X-Forwarded-For; it does not protect against X-Real-IP or RFC 7239 Forwarded. If IP-based policy is part of your security boundary, configure trusted proxies explicitly and prevent direct access to the S3 API port. Multi-node deployments should allow their own node addresses. See Client Source Address Trust.

S3 and Storage Correctness

Multipart upload

  • CompleteMultipartUpload accepts the S3 full-object checksum mode when the completed request supplies no per-part checksums and the upload metadata does not require them.
  • The checksum of a zero-length multipart object is retained instead of being discarded as empty metadata.
  • Part numbers must be strictly increasing. Duplicate entries such as [1,1] now return InvalidPartOrder instead of consuming the upload and assembling the same part twice. Legal part lists with gaps or a non-1 start remain accepted. See Duplicate Part Numbers.

Object reads and buffer ownership

  • erasure reads again pool buffers only where ownership permits reuse;
  • update downloads return caller-owned buffers instead of exposing data that can be overwritten after return;
  • the old HTTP streaming helpers orphaned by ReadMultiple removal are deleted after reference and platform-tag checks.

HTTP response tracking and S3 Select

  • trackingResponseWriter.Flush() delegates to the underlying flusher and commits the response state correctly;
  • the first implicit write records HTTP 200, preserving audit and metric accuracy;
  • S3 Select tests no longer race a client parser against response-body ownership;
  • CSV, JSON, and Parquet selection paths remain covered, including range/error and keep-alive behavior.

The inherited silent-flush regression called out in SILO 20260618 is therefore fixed in this release.

IAM, Versioning, and Audit Behavior

  • DeleteObject and each entry in DeleteObjects evaluate s3:versionid against the effective version selected by the server.
  • Request tags can no longer impersonate existing-object tags during policy evaluation.
  • The merrs tag is restored when dangling-object deletion records are emitted, preserving the intended audit classification.
  • Bucket-policy and IAM paths share the hardened condition-source rules while retaining their established S3 routing and error behavior.

Notification Configuration

  • registers the NATS user_credentials, nkey_seed, and tls_handshake_first keys read by the parser;
  • separates the legacy NATS environment-variable spelling from the stored config key;
  • repairs NATS migration round trips and the AMQP immediate/internal mapping;
  • adds a mechanical audit that compares keys read and written by notification code with each subsystem’s registered schema;
  • quotes libpq connection-string parameters so whitespace, quotes, and backslashes retain their intended value;
  • prevents invalid-key diagnostics from echoing secret values.

See Notify Keyspace Registration.

Warning

Known legacy migration limitation

The inherited Postgres and MySQL legacy migration functions still write the unregistered host, port, username, password, and database fields. A migrated configuration can therefore fail validation on the next load, and notification target loading is fail-fast across subsystems. This predates 20260804, but upgrades from pre-connection-string database notification configurations must be reviewed and converted before restart. The stored password field may contain a plaintext database password.

Components and Dependencies

  • Go 1.26.5: includes security fixes in crypto/tls and os plus compiler, runtime, networking, and syscall corrections.
  • klauspost/compress 1.18.7: refreshes the compression stack used by object and archive paths.
  • Apache Thrift 0.24.0: updates the dependency compiled through Parquet support.
  • go-systemd 22.6.0: deliberately retained instead of 22.7.0 because the later version introduced a NetBSD clock dependency incompatible with the supported cross-build matrix.
  • SILO Console 2.0.0: the embedded console is selected from pgsty/silo-console while preserving the compatible github.com/minio/console import path.
  • silo-pkg 3.11.0: provides the companion policy, LDAP, certificate, RNG, and time-format fixes while preserving the github.com/minio/pkg/v3 module path.
  • mcli 20260804: the embedded client comes from pgsty/mc; release images expose it as mcli and keep the mc compatibility alias.

See the companion release notes for silo-pkg 3.11.0, mcli 20260804, and SILO Console 2.0.0.

Build, CI, and Packaging

This release rebuilt the release pipeline for reproducibility and supply-chain integrity:

  • Graceful container shutdown on every path. The entrypoint’s custom UID/GID branches now exec into the server so it runs as PID 1 and receives SIGTERM directly; previously those branches left an intermediate shell as PID 1 and the server was killed at the container stop timeout. A CI smoke test builds the release runtime image and asserts graceful shutdown on both the default and drop-privilege paths.
  • Reproducible binaries. Release binaries no longer embed the build machine’s GOPATH/GOROOT, so -trimpath holds and a third party rebuilding the tag gets matching bytes. The published Linux binary contains no build-host path.
  • Hardened release workflow. The release tag is passed through the environment and whitelisted rather than spliced into the shell, the build is checked out at the tag being released, and an untracked shadow GoReleaser config that could publish or move latest out of band was removed.
  • Honest gates. CI gates build, vet, unit tests, lint, generation drift, race tests, and cross-compilation; the cross-compile matrix is aligned to the exact set of published targets; and the lint and dependency-install steps now fail on real errors instead of masking them.
  • Signed, canonical packages. RPM, DEB, and APK packages are produced with nFPM under the PGSTY identity, the systemd unit installs at /usr/lib/systemd/system/minio.service with Type=notify, and RPMs are GPG-signed offline with the PGSTY maintainer key (fingerprint 9592A7BC 7A682E73 33376E09 E7935D8D B9BD8B20).
  • Release and container publication remain separate gates. GoReleaser produces the platform archives, checksums, and packages; the multi-architecture image is published on demand from the finished release. A local snapshot does not prove a public release or image exists.

Compatibility and Upgrade Notes

  1. Keep every node on one release during a cluster rollout. Internode validation changed across storage-REST and Grid surfaces; mixed binaries were not production-tested.
  2. Audit custom IAM policies. Add the bare bucket ARN for the twelve protected bucket writes. Use MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on only as a temporary migration control.
  3. Configure client-address trust deliberately. If aws:SourceIp or audit attribution matters, set MINIO_API_TRUSTED_PROXIES and close direct network paths around the proxy.
  4. Review legacy database notification settings. Convert Postgres/MySQL host/user/password fields to the supported connection-string format before restart.
  5. Expect duplicate multipart completion entries to fail. Clients sending the same part number more than once now receive InvalidPartOrder instead of a corrupted successful object.
  6. Use the matching mcli. The 20260804 client disables self-update and must be upgraded through packages or GitHub Releases; mcli update remains as a compatibility command but exits non-zero.
  7. RPM users can enable signature verification. Packages are signed with the maintainer key above; import it before enabling gpgcheck for the SILO packages.

Verification

Changes were reviewed against the tagged commit and re-verified rather than trusted from prior reports:

  • git diff --check, gofmt, module verification, and YAML/shell syntax;
  • go build ./..., go vet ./..., project lint, and govulncheck ./...;
  • full go test ./..., the complete race suite, and repeated race tests over storage, policy, notification, HTTP tracking, and S3 Select changes;
  • generator idempotence plus deliberate stale-source and untracked-output counterexamples;
  • cross-compilation across every published target;
  • Linux AMD64 native full tests, targeted race tests, live S3/mcli smoke tests (create/upload/download/copy, range, versioning, delete markers, health checks, graceful shutdown, restart persistence), and systemd notify behavior;
  • release-artifact verification: GitHub CI green on the released HEAD, reproducible binaries with no build-host path, the systemd unit installed at /usr/lib/systemd/system, and RPM signatures validated with rpmkeys --checksig.

govulncheck found no vulnerability reachable from the Server or mcli code. One module-level notice remains for the unmaintained golang.org/x/crypto/openpgp package (GO-2026-5932); that package is not imported into these binaries.

Validation boundaries

The following were not proven by this release and must not be inferred from cross-compilation or unit tests:

  • native Windows execution and Windows filesystem semantics;
  • Intel macOS and physical Linux ARM64 hosts;
  • a production multi-node rolling upgrade, site replication, or lifecycle expiration run;
  • real reverse-proxy chains and direct-ingress isolation;
  • external LDAP, OIDC, KMS, STS, Postgres, MySQL, NATS, and AMQP services;
  • installing and upgrading the signed package under a real systemd host.

Artifacts

  • GitHub release RELEASE.2026-08-04T00-00-00Z with checksummed platform archives for Linux, Darwin, and Windows on amd64 and arm64;
  • RPM, DEB, and APK packages under the PGSTY identity, with GPG-signed RPMs;
  • docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z and the release-selected latest tag, published on demand from the release;
  • matching SILO Console 2.0.0, silo-pkg 3.11.0, and mcli 20260804 references.

Selected Changes

  • ca7baa670, 80e8eaa42, b6f70ab08: validate internode paths, erasure metadata, and allocation sizes
  • a36fd8fff: contain panics in deadline-bounded storage work
  • 2f55347f7: bind S3/IAM policy conditions to effective request values
  • 744a9dcd7: bind s3:versionid to the effective object version
  • 97b7d2804: enforce the bucket/object resource boundary
  • fe6dc4780: add the trusted-proxy client-address boundary
  • 22c1e41fd: reject duplicate multipart part numbers
  • c8590413f, 3e14733f1: restore full-object and zero-length multipart checksum behavior
  • 8069a32ac, 65795ee1f: restore response commit and streaming flush semantics
  • 162ded343, 0c14d8151: repair notification key registration and libpq quoting
  • 924717926, 89d346bf5: restore safe buffer pooling and returned-buffer ownership
  • 3b8a55dee: exec into the dropped-privilege process so signals reach the server
  • 2ca4971d9: stop stamping the build machine’s paths into the binary
  • 4c185d5a6, e064b5555: harden the release workflow and remove the shadow config
  • aa5139369: install the systemd unit under /usr/lib
  • 11d79fddc, ca674a696, 021110b45, d88f46cce: gate build, vet, tests, lint, generation, race, and cross-compilation, and smoke-test the release image

7 - mcli 20260804 Released

Self-update disabled, SUBNET debug-log credential leak fixed, containers built from fork source, packaging migrated to nFPM, and signed RPMs.

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

This is the first release of the pgsty/mc community fork since 20260417. It fixes a credential leak in debug logging, severs every remaining connection between the client and upstream release channels, moves containers and packages onto artifacts this fork builds itself, and migrates packaging from MinIO’s pkger to standard nFPM — with GPG-signed RPMs for the first time.

Upstream minio/mc was archived in July 2026. Its final commit, 77f82e18, is exactly this fork’s base, and upstream never cut a release containing it — so this build is strictly newer than any official mc binary ever published.

Warning

Behavior change

mcli update self-update is disabled in this fork. The command remains for script compatibility and still accepts its original arguments, but it no longer contacts the network or replaces its own binary; it prints an explicit notice and always exits with status 1. Upstream mc update exited 0 when already up to date, so drop the call from any script that treats a non-zero exit as failure. Upgrade through the Pigsty package repository or GitHub Releases.

The automatic version check that ran against upstream release feeds on every invocation has also been removed entirely. The MC_UPDATE and MINIO_UPDATE environment variables are no longer consulted.

Major Changes

  • Self-update disabled, upstream release channels severed: the minio/selfupdate and aead.dev/minisign dependencies and all binary-replacement logic are gone, along with the update notifier and the FIPS/non-FIPS update paths. The update command survives as a compatibility shell, and the runtime helpers (Docker / DCOS / Kubernetes / source-build detection) moved to a dedicated cmd/runtime-info.go. The client previously reached out to upstream release feeds on every invocation to print an upgrade hint; there is now no outbound release probing at all.
  • Containers and artifacts fully localized: the default image is built from the checked-out fork source, and hotfix binaries are copied from the local build context — no upstream prebuilt binaries are downloaded. The upstream publishing files Dockerfile.release, Dockerfile.release.old_cpu, and docker-buildx.sh were removed, and the obsolete MinIO hotfix upload target is disabled.
  • Packaging migrated to nFPM: replaced MinIO’s pkger with standard nFPM. Artifact layout and install path are unchanged (/usr/local/bin/mcli, package name mcli, YYYYMMDDHHMMSS.0.0 version scheme), but the vendor is now PGSTY, the license uses the SPDX identifier AGPL-3.0-or-later, and the Debian Section moved from empty to utils.
  • RPMs are now GPG-signed: RPMs are signed offline with the maintainer key (fingerprint 9592A7BC7A682E7333376E09E7935D8DB9BD8B20). All package metadata is asserted before signing, and the signature is re-verified with checksums regenerated afterwards. DEB and APK packages carry no package-level signature; their trust anchor lives at the repository layer.
  • Build provenance hardened: every previously published binary was stamped by the Go toolchain as built from a modified working tree (vcs.modified=true), which broke the link between an artifact and its Git tag. This release fixes that and adds an enforcing check to both the release and test pipelines, so every binary is traceable to an exact commit.

Security Fixes

  • SUBNET credentials redacted in debug logs: with --debug enabled, SUBNET HTTP exchanges are printed in full. Previously the api-key / api_key query parameters, authentication headers, and response bodies all reached the log in clear text — and SUBNET’s authentication and registration endpoints return API keys, licenses, and tokens in their responses. Both parameter spellings and duplicate values are now masked uniformly, sensitive response headers are redacted, and SUBNET response bodies are excluded from debug dumps entirely. The leak is inherited from upstream and present in every previous release, upstream mc included: if you have ever shared --debug output of SUBNET commands (mcli license ... / mcli support ...), treat the API keys and licenses in it as exposed and rotate them.
  • Redaction isolated from caller state: debug tracing now dumps copies of the request and response, so redaction cannot mutate objects the caller still holds. Zero-length, fixed-length, and unknown-length response bodies are all covered, and callers can still read the response normally.

Dependency Updates

This cycle’s dependency work is security maintenance, not routine hygiene: every bump below except the term / mod / sync / tools refresh closes at least one published advisory in the Go vulnerability database, and govulncheck reports zero known vulnerabilities reachable from this release’s code. No security advisory has ever been published for minio/mc, minio-go, madmin-go, or minio/pkg themselves.

  • Go build baseline upgraded from 1.26.2 to 1.26.5 (the newest 1.26.x at release time), picking up the 1.26.3–1.26.5 security batches — including GO-2026-4970 (symlink-based root escape in os) and GO-2026-5856 (Encrypted Client Hello privacy leak in crypto/tls), the two most relevant to an S3 client that writes local files and speaks TLS.
  • github.com/klauspost/compress from v1.18.5 to v1.18.7 (closes GO-2026-5841).
  • github.com/prometheus/prometheus from v0.310.0 to v0.311.3 (closes GO-2026-5264, GO-2026-5381, GO-2026-5710).
  • google.golang.org/grpc from v1.79.3 to v1.82.1 (closes GO-2026-6061), with the genproto family refreshed alongside.
  • The golang.org/x/* family refreshed across the board: crypto v0.49.0v0.53.0 (the 14-advisory GO-2026-5005…5033 batch), net v0.52.0v0.56.0 (GO-2026-5025…5030 and GO-2026-5942), sys v0.42.0v0.46.0 (GO-2026-5024), text v0.35.0v0.39.0 (GO-2026-5970), plus term, mod, sync, and tools.
  • Removed aead.dev/minisign and github.com/minio/selfupdate, and synchronized the third-party credits file.

Engineering and Delivery

  • Integration test dependencies pinned: CI no longer downloads the MinIO server from a mutable upstream URL. It now uses a versioned pgsty/minio release archive verified by its SHA-256 digest, with Go pinned to 1.26.5.
  • Release pipeline verification: a packaging validation workflow compares the binary inside all three package formats byte-for-byte against the build output, and checks package names, checksums, architecture fields, and every metadata field. The expected RPM metadata is sourced from the signing script itself, so configuration drift cannot strand a release part-way through signing.
  • CI supply-chain hardening: every GitHub Action is pinned to a commit SHA with dependabot keeping them current, workflow permissions are narrowed to read-only, and a stale workflow pointing at the upstream organization’s project board was removed.
  • Documentation: the English and Chinese READMEs now state this fork’s distribution channels and self-update policy explicitly, and installation instructions that would silently install upstream mc were removed.
Note

Known issue

mcli watch (bucket event notification) receives no events against any published pgsty/minio server release. The cause is a silent streaming-flush regression on the server side, inherited from upstream — it is not a client problem, and the previous mcli release is affected identically. The fix was merged to the server’s master on 2026-07-29 but has not shipped in a published server release. See the SILO 20260618 release notes and PR #34.

Separately, this fork inherits upstream’s unfixed defects, and with the upstream repository archived they can only ever be fixed here. The most serious is minio/mc#5139: mirror --remove --watch can delete a live object from the target when a non-current version of it is removed from the source. Exercise caution combining --remove --watch on versioned buckets.

  • 9603ee3: fix: redact SUBNET secrets in HTTP debug logs
  • f6ae2b0: fix: disable self-update in Pigsty builds
  • c05a6e4: build: update Go deps and toolchain to 1.26.5
  • 1f105aa: build: use local fork artifacts for containers
  • 1182da5: ci: pin fork integration test dependencies
  • 9ee207f: docs: clarify Pigsty fork distribution channels
  • 0686cd8: fix: isolate SUBNET debug redaction
  • ad10a2a: build: complete local Docker context isolation
  • 5f54221: docs: update mc README and cn version
  • 02c0305: build: migrate release packaging to nFPM
  • 4c4dcc4: build: harden release provenance and package metadata

8 - 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.

Note

Note

Known issue: this release — like every earlier community release since RELEASE.2025-12-03T12-00-00Z — carries a silent streaming-flush regression inherited from upstream that breaks mc watch / bucket-notification listeners and S3 Select keep-alives. There is no workaround. The fix was merged to master on 2026-07-29 but has not shipped in a published server release; see PR #34 for the implementation.

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

9 - 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

10 - 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

11 - 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

12 - 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

13 - 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

14 - 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