mcli 20260901 Released
Published: 2026-09-01 · Version: RELEASE.2026-09-01T00-00-00Z
Twenty-six days after mcli 20260806, this release carries everything the client accumulated since. The toolchain moves to Go 1.27.0 and the shared dependency stack is aligned with the SILO server, with the SILO package imported under its own module path so go.mod says what the binary is built from. The first genuinely new command since the fork arrives — mcli checksum verify, a read-only audit of stored S3 additional checksums against the bytes an endpoint actually returns. The panic that made --json admin prometheus metrics unusable is fixed. And every path that can print a credential — --debug traces, admin trace, error messages, JSON error documents, checksum reports — was rewritten fail-closed after three rounds of adversarial review. The release process changed as much as the code: artifacts are produced only from a GPG-signed tag at the exact tip of main with green CI for that commit, ship LICENSE, NOTICE, and a generated CREDITS in every format, carry Sigstore build provenance, and land in immutable GitHub releases.
The release was built from ddac5d58, the merge of pgsty/mc#24: 49 commits, 262 files, +12,487 / −6,089 lines since RELEASE.2026-08-06T00-00-00Z.
Behavior changes
- macOS 13 is the new floor. Go 1.27 links darwin binaries with a minimum OS version of 13.0; the darwin archives no longer run on macOS 12 and earlier.
mcli --json admin prometheus metricsworks again and returns metric families in deterministic name order. Plain-text metrics output is unchanged.pipewith empty standard input now performs a regular zero-byte PUT instead of a one-part multipart upload. The resulting ETag isd41d8cd98f00b204e9800998ecf8427erather than the<md5>-1form. Non-empty input is unchanged.- Policy writes are validated strictly on the client —
admin policy create,admin user svcacct add/set,idp ldap accesskey create/edit. A bare ARN such asarn:aws:s3:::is rejected (writearn:aws:s3:::*to mean every resource under that prefix); a named policy withoutVersionis rejected; an empty policy is rejected; mixing S3 and admin actions in one statement is rejected. The last three were already refused by the server, so only bare ARNs change what can be stored. Reads of existing policies remain permissive. Session policies for service accounts may still omitVersion, andadmin user svcacct set --policystill accepts an empty document — that is the way to clear an inline policy and return the account to its inherited one. - A malformed global flag before the command name is now an error.
mcli -H "bad" ls,--resolveand--limit-*values that fail to parse at the app level used to be silently ignored and the command ran without them. They now fail with the same message the command-level form produces, reported once on stderr — or as a JSON error document under--json— where the CLI library used to echo the text to stdout first. - Error messages no longer echo secrets.
alias setreports only the length requirement for a bad secret key;MC_CONFIG_ENV_FILEparse errors name a line number instead of repeating the line; SSE-C key errors withhold the key;admin config setmasks secret-bearingkey=valuepairs; remote replication targets are printed without credentials; a malformed--custom-headerentry is reported by position, never by value. - Support artifacts are private. Files written by
support diag,support profile,support perf,support inspect,admin cluster bucket export, andadmin cluster iam exportare created with mode0600, including the rotated backup of a previous file. They were0644or0666before. --debugshows less. AnAuthorizationheader keeps only its scheme (AWS4-HMAC-SHA256 **REDACTED**); tokens, cookies, API keys, SSE-C keys and every--custom-headervalue are redacted; 2xx response bodies are no longer dumped.admin traceoutput is redacted the same way in both its default and its verbose rendering: header values by name, query strings, bodies, error text and annotations.- The User-Agent reports
minio-go/v7.3.0(the SDK’s version constant) instead ofminio-go/v7.0.98.
Major Changes
- Go 1.27.0 across the module, the container build stage, Linux/macOS/Windows CI, cross-builds, tidy checks, and vulnerability analysis. The Go-maintained module family is current:
x/net0.58.0,x/sys0.47.0,x/term0.45.0,x/text0.41.0,x/crypto0.55.0,x/mod0.40.0,x/sync0.22.0,x/tools0.49.0.golangci-lintleaves the product module graph and is installed independently at the pinned v2.13.1. - The SILO package under its own module path.
go.modnow requiresgithub.com/pgsty/silo-pkg/v3v3.13.0 directly. The client’s policy validation depends on helpers that exist only there —policy.ParseConfigStrictandpolicy.Resource.IsBareARN— and areplaceof upstreamminio/pkgwould not have been inherited by any project consuming this module as a dependency (pgsty/mc#12), so the requirement is declared honestly instead. Upstreamminio/pkgsurvives only as an indirect dependency ofcolorjson, held at v3.6.1 by the dependency-floor check, andgomodguardblocks the upstream import path from returning.minio-gomoves from v7.0.99 to the upstream pseudo-versionv7.3.1-0.20260828014306-0e78d3f18efethatsilo-pkgitself builds against; the interimpgsty/silo-gofork is retired, its one functional change having been merged upstream. The rest of the shared stack follows the server: the etcd client modules move from 3.6.9 to 3.7.1, which carries the fix for GO-2026-6107 / CVE-2026-73500;grpc-gateway2.29.0,zap1.28.0, andgo.yaml.in/yaml/v33.0.5 follow.go-systemddeliberately stays at 22.6.0 through SILO’s shared portability pin — 22.7.0 does not compile on NetBSD. - Runtime version floors are guarded. A first dependency pass let the removal of development tools silently lower a dozen runtime selections — among them
shoenig/go-m1cpu, whose older releases crash during initialization on newer Apple Silicon when CGO is enabled — and was rejected.buildscripts/check-dependency-floorsnow compares every requirement ingo.modagainst the previousRELEASE.*tag and fails the build on a regression; the only permitted exception is thego-systemdpin. mcli checksum verify(design). Verifies stored S3 additional checksums — CRC32, CRC32C, CRC64NVME, SHA1, SHA256, of typeFULL_OBJECT— against the logical object bytes returned by the object API, for one object, a prefix with--recursive, all versions with--versionsor an exact--version-id, or a JSON Lines candidate manifest. It reads withIf-Matchon unversioned objects and re-stats afterwards, so a write that races the verification is reported asUNKNOWN_OBJECT_CHANGEDrather than as a mismatch. Each object yields one ofMATCH,MISMATCH,NO_CHECKSUM,WOULD_VERIFY(dry run), one of tenUNKNOWN_*classifications, or one of threeSKIPPED_*results; the summary carriesobjects, averifiedcount (MATCH+MISMATCH, the only results that actually recomputed a checksum), per-status counts andincomplete.--fail-onacceptsmismatch,unknown,no-checksum(fails when any object lacks a checksum or when nothing was verified at all),any(default: mismatches, unknowns, and objects skipped by--max-size), ornone.--reportwrites the same records to a new0600JSON Lines file. Results go to stdout whether or not stdout is a terminal, and with--jsonthey are compact JSON Lines outside a terminal — the non-TTY silence reported as pgsty/mc#5 is fixed. Dry runs,--max-workers,--max-size, time filters, and SSE-C through--enc-care supported. The command is verified against a real SILO server in CI.- Credential redaction, fail-closed. Three rounds of adversarial review by Codex showed that the original redaction — and its first two rewrites — recognized the shapes it expected and let everything else through: access keys containing a slash, an
Authorizationvalue whose first field is empty so the second one hides, non-hex signatures, secrets tucked into the credential scope orSignedHeaders,Proxy-Authorization, cookies,X-Api-Key, response trailers, a redirectLocationwith userinfo, a server that reflects request headers into an error body, a JSON-escaped secret inside--jsonerror output, a short token that appears twice. The final design has three rules and no exceptions. AnAuthorization-class value keeps only its scheme. A header whose name looks like a secret, or that the caller supplied through--custom-header, is redacted without inspecting its content. Text the client did not generate itself — response bodies, server error messages, trace notes — is scanned for credential shapes and then scrubbed of every secret literal the process has learned. That last layer is backed by a process-wide secret registry: alias secret keys and session tokens,MC_HOST_*values, SSE-C keys in every encoding, tier and service-principal secrets, GCS credential files, LDAP passwords and the STS credentials they return, SUBNET tokens,--secret-key/--account-key/--api-keyflags, and secret-bearingkey=valuearguments are registered as they are read, before validation and before any network call. Occurrences are collected as intervals and merged before replacement; JSON error documents are scrubbed on the decoded value tree, string leaves only, so keys and compact non-terminal formatting survive. Forty-odd adversarial cases, a black-box test that runs the real client against an endpoint that reflects everything it receives, and live captures against a SILO server pin the result. admin traceredaction. Server-supplied header maps for other clients’ requests take a path the S3 tracer never touched. Both the human-readable and the JSON rendering, in the default and the verbose form, are now produced from redacted copies — the event is shared between them and never mutated. Query strings (a presigned URL’s signature, a session token in the first parameter), bodies, error text and annotations are scrubbed with the event’s own header values, so a token another client sent cannot reach privileged trace output. Headers are redacted by name: a--custom-headeranother client sent under a name that does not look like a secret is shown as the server saw it.
Fixes
- JSON metrics no longer panic.
mcli --json admin prometheus metricsfailed for both the v2 and v3 metrics APIs withpanic: Invalid name validation scheme requested: unsetin the 20260806 client.madmin-gobuilt the zero value of the Prometheus text parser, whichprometheus/common0.66 and newer reject. The client now constructs the parser with UTF-8 validation, returns ordinary parse errors instead of panicking, and emits families in name order. pipewith empty input performs a regular PUT; the one-part multipart upload it used to issue for zero bytes was needless and produced a multipart-style ETag.- RPM payload validation aligned with the signing script, so an RPM whose contents do not match the release metadata is refused before it is signed.
sqlno longer panics after printing its rows. The SDK’s select reader closes the HTTP response itself on every terminal event, and the client closed it a second time; on a zstd-compressed response the two drains raced and about two in five queries against a SILO server ended inpanic: invalid memory addresswith exit status2after the rows had already been written (pgsty/mc#23). The client now cancels the request and drains the result pipe instead of closing the response again; 820 consecutive queries on a live server ran without a panic where the previous client failed 21 of 50.
Hardening
- Operator-produced files are no longer world-readable.
support diagwarned that its archive may contain sensitive environment information and then wrote it0666; the sharedmoveFilehelper relaxed every0600temporary file to0644on its way to the final name, and a0644left by an earlier client was carried into the rotated backup even when source permissions were preserved. Every support artifact is now created0600and chmodded before the first byte is written. Test_FullSuitefails on panic. The functional suite recovered from panics to run cleanup and then reported success; a panic anywhere in the suite, including in the binary build, was invisible to CI.- A leftover
play/alias in the new command’s help — the only file in the repository still advertising MinIO’s operated demo alias — is replaced, and the brand gate now rejects bare aliases in command examples, not only hostnames. - AGPL notices completed on six fork-authored files that carried only the first half of the license text.
Engineering and Delivery
- Third-party notices in every artifact.
CREDITSis generated bybuildscripts/gen-credits.shfrom the union of the runtime modules linked into all six GoReleaser targets and is compared in CI so a dependency change cannot leave it stale.LICENSE,NOTICE, andCREDITSship in the GoReleaser archives, the DEB/RPM/APK packages, and every container image under/licenses/. - CI runs on
main.GoandCrosscompileran only on pull requests, so the merge commit — the one that gets tagged — was never tested; both now run on every push tomain. Their concurrency group was keyed ongithub.head_ref, which is empty outside pull requests, so every manual run cancelled every other; it is now keyed on the pull request number with the ref as fallback.VulnCheckalso runs weekly, because a fork of an archived upstream can go weeks without a commit. The functional suite in CI now targets SILORELEASE.2026-08-06T00-00-00Z. - Release only from signed tags. The artifact workflow has no manual trigger; artifacts come from a pushed
RELEASE.*tag, and the event SHA, the commit the tag points to, and the checkout must all agree.verify-release-tag.shrequires an annotated tag with a valid OpenPGP signature from the expected key — fingerprint and key location come from repository variables rather than from the tree the tag points at, and the imported key must match the expected fingerprint.check-release-commit.shrequires the tagged commit to be the exact tip oforigin/mainand demands a successful push-to-mainrun of four workflows for that SHA, matched by workflow file path; pull request and manual runs are not evidence.check-release-state.sh(pgsty/mc#6) runs serially per tag, replaces exactly one existing Draft from scratch, refuses a published release, and re-checks for a single Draft before packages are uploaded. Every one of these decisions lives in a shell script with fixture coverage; the fakeghin the tests pins both the verdicts and the arguments passed, verbatim. - Provenance and immutability. Archives, checksums, DEB and APK packages carry Sigstore build provenance (
gh attestation verify <file> --repo pgsty/mc). RPMs are excluded on purpose:sign-release-rpms.shrewrites their bytes with a detached GPG signature from key9592A7BC7A682E7333376E09E7935D8DB9BD8B20while the release is still a Draft, and an attestation made earlier would name a digest no published RPM has. GitHub releases are immutable once published; fixing an asset means a new tag. Thepgsty/mccontainer image is built only from attested archives, aftergh attestation verifybinds each one torelease.yml@refs/tags/<tag>, the release commit digest, GitHub-hosted runners, its own filename and its SHA-256; the unpacked binary’s ELF machine type must match the archive’s architecture; each per-architecture image is pulled, run, and checked for the tag before the manifest is assembled, andlatestis written only from the two-architecture manifest. The UBI base image is pinned by digest and kept current by Dependabot. - Repository governance.
mainrequires a pull request and all eight checks by exact name (DCO, Go on Linux/macOS/Windows, vetchecks, Crosscompile, VulnCheck, Test Release — the last now unfiltered by path, on every pull request and push);RELEASE.*tags can be created only by administrators and can never be updated or deleted; secret scanning, push protection, Dependabot alerts and security updates are on. The project has a single maintainer, so the enforceable policy is “pull request gated on required checks” rather than independent human review; the README says so. - Verified before tagging. tidy/verify, dependency floors,
go vet, golangci-lint, actionlint, brand policy,govulncheckwith zero reachable vulnerabilities; native Apple Silicon CGO execution and the full race suite; the cross-build matrix plus FreeBSD, OpenBSD, NetBSD, and Windows ARM64; the HTTP and HTTPS functional suites against the published SILORELEASE.2026-08-06T00-00-00Z, including SSE-C, multipart, watch, mirror, presigned requests, users, and policies. On top of that, three review rounds by Codex (GPT-5.6) with independent verification by Claude Code, and a final pass by six independent reviewers over the whole diff since 20260806 — credential redaction,checksum verify, behavior changes, the release chain, the module-path sweep, and a black-box run of the candidate against the 20260806 binary on a live server. 34 of the 49 commits respond directly to something those reviews found, each pinned by a regression test before the fix. Beyond the credential work, the reviews caught — and this release fixes — a--fail-on no-checksumthat could exit0when nothing had been verified, a release gate that would have accepted runs which never built the release commit, an attestation subject check that would have failed the first real container publish, a moment wherelatestpointed at an amd64-only manifest, a defaultadmin tracerendering that printed query strings unredacted, credential shapes that rewrote ordinary prose in error messages, asvcacct setthat could no longer clear a policy, and the long-standingsqlpanic.
Compatibility
The full --help tree of this release was diffed against the 20260806 binary: the only difference is the new checksum command. Side by side on the same server, head, cat, get, cp, mirror, find, share, sql, stat, and ls produce identical output and JSON field sets; twenty real-world policy shapes are accepted or rejected identically except for bare ARNs; admin policy info --json is byte-identical. The mc command name and the mcli package name; the ~/.mc / ~/.mcli configuration directories; the github.com/minio/mc module path and all import paths; MC_* environment variables; protocol headers (x-minio-*) and the minio-go user-agent prefix; the minio tier type; the .part.minio transfer suffix; the minio-job scrape job name; and the package formats, asset naming, and YYYYMMDDHHMMSS.0.0 version scheme are unchanged. The client remains compatible with MinIO servers and other S3-compatible endpoints. Projects that consume this module as a Go dependency now resolve github.com/pgsty/silo-pkg/v3 directly and need no replace of their own for it.
Known issues and boundaries
checksum verifyreportsUNKNOWN_READ_ERROR, notMISMATCH, when the server’s own integrity checks refuse a read — SILO’s bitrot protection rejects a corrupted shard before the object API returns it. AMISMATCHmeans the stored checksum describes different bytes than the object API returns, which is the historical write-path defect the command exists to find; anUNKNOWNis never reported as aMATCH.- Access key IDs are redacted wherever they appear inside a credential structure, but a server that reflects a bare access key into an error message will still have it printed: it doubles as the user identifier in legitimate messages. Secrets shorter than three characters are not registered. When the secret key equals the access key — the default
minioadmin:minioadmin— the identifier is redacted from error text too, because the string is the secret. --jsonerror documents list the keys ofcause.erroralphabetically rather than in struct order; the keys and values are unchanged.mcli sqlreports a failed query on stderr but still exits0(pgsty/mc#25); per-object errors are reported without failing the run so a recursive query can continue. Unchanged from upstream.govulnchecktraces GO-2026-5932 in the indirect dependencyx/crypto/openpgp; no fixed version exists and the client does not call it.- minio/mc#5139 continues to apply:
mirror --remove --watchcan delete a live object from the target when a non-current version of it is removed from the source. - Quality debt that does not block the release — archive reproducibility, test-tool portability, deferred coverage of KMS / SSE-S3 / multi-site replication — is tracked in pgsty/mc#21.
Related Commits
- 6686b64: chore(deps): align the SILO Go dependency stack
- 423490d: fix: stop JSON metrics from panicking
- 14b05e6: build: generate and package third-party notices
- e938cea: fix: align RPM payload signing validation
- 8158605: release: adopt Silo Go SDK v7.3.1
- d6ba236: ci: allow manual release workflow runs
- b6b2655: ci: release an existing tag on dispatch
- 70a2950: fix: preserve embeddable pkg compatibility
- aa07b1c: feat: add read-only object checksum verification
- 1b39423: fix: preserve checksum output in pipelines
- a31a42b: docs: record checksum verify output contract
- 2121081: fix: validate policy writes strictly
- 712bf3a: fix: use regular PUT for empty pipe input
- 9a3805d: ci: make release retries tag-idempotent
- 7bc9843: test: make report permissions portable
- 2d5abc2: chore: complete the AGPL notice on fork-authored files
- f5c5dc9: build: import the SILO package under its own module path
- a8e7eb4: ci: test main on push and stop dispatch runs cancelling each other
- b75d28b: fix: stop checksum verify help from advertising the play alias
- c05da7b: chore: drop configuration for bots that are not installed
- a29b455: fix: stop –debug from printing credentials
- 08244df: fix: keep secrets out of error messages and traces
- 30a7ea0: fix: stop writing support artifacts world-readable
- 14bd724: test: fail the full suite when it panics
- 4abbb3a: feat: expose how much checksum verify actually verified
- f484d0d: ci: make a release prove its commit, and pin what it builds from
- 2c33a4e: chore: ignore the local consumer-module review scratch directory
- bdc742a: fix: close the remaining –debug credential leaks
- 3998392: fix: withhold SSE-C keys and credential URLs the parser rejects
- c1358c4: fix: force 0600 on rotated support artifacts too
- 45e5144: fix: fail –fail-on no-checksum when nothing was verified
- 3674933: ci: make the release gate provable and its claims true
- 8b7b2e9: test: exercise checksum verify against a real server
- 4b36b0a: fix: make credential redaction fail-closed and scrub final output
- a6c14da: ci: verify build provenance before repackaging archives into images
- 915cfd9: fix: reject a malformed app-level global flag instead of ignoring it
- 81e2767: fix: rebuild SigV4 traces from known fields and widen secret scrubbing
- 625713c: fix: keep only the scheme of a signed header and scrub JSON structurally
- ec12b64: fix: redact admin trace output and register every credential ingress
- 1445e88: ci: release only from a signed tag at the tip of main, verify exact provenance
- f188939: fix: close final credential and release verification gaps
- f5b88dd: fix: redact the default admin trace and stop the shape sweep mangling prose
- 59989f8: fix: let svcacct set clear a policy again, report bad global flags once
- d38412a: fix: classify an SSE-C refusal with a key as a read error, keep policy and size messages clear
- b291b53: fix: register a URL password as written, keep listing parameters visible in traces
- 9dfca8f: test: drive the SSE-C refusal classification through the verify path
- 76b2e89: fix: keep an unquoted DSN password whole and match auth schemes case-insensitively
- 328efee: fix: register the password before the first separator of an unquoted DSN payload too
- 7057705: fix: avoid double-closing S3 Select responses